awk: do not allow $(-1)
[oweals/busybox.git] / editors / awk.c
index 72eca245f0d098ae8291004b81bc0ce60bbd3761..bafc9ba1d382b6bbb2651469aefd12b1161625b5 100644 (file)
@@ -4,8 +4,46 @@
  *
  * Copyright (C) 2002 by Dmitry Zakharov <dmit@crp.bank.gov.ua>
  *
- * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
+//config:config AWK
+//config:      bool "awk (22 kb)"
+//config:      default y
+//config:      help
+//config:      Awk is used as a pattern scanning and processing language.
+//config:
+//config:config FEATURE_AWK_LIBM
+//config:      bool "Enable math functions (requires libm)"
+//config:      default y
+//config:      depends on AWK
+//config:      help
+//config:      Enable math functions of the Awk programming language.
+//config:      NOTE: This requires libm to be present for linking.
+//config:
+//config:config FEATURE_AWK_GNU_EXTENSIONS
+//config:      bool "Enable a few GNU extensions"
+//config:      default y
+//config:      depends on AWK
+//config:      help
+//config:      Enable a few features from gawk:
+//config:      * command line option -e AWK_PROGRAM
+//config:      * simultaneous use of -f and -e on the command line.
+//config:      This enables the use of awk library files.
+//config:      Example: awk -f mylib.awk -e '{print myfunction($1);}' ...
+
+//applet:IF_AWK(APPLET_NOEXEC(awk, awk, BB_DIR_USR_BIN, BB_SUID_DROP, awk))
+
+//kbuild:lib-$(CONFIG_AWK) += awk.o
+
+//usage:#define awk_trivial_usage
+//usage:       "[OPTIONS] [AWK_PROGRAM] [FILE]..."
+//usage:#define awk_full_usage "\n\n"
+//usage:       "       -v VAR=VAL      Set variable"
+//usage:     "\n       -F SEP          Use SEP as field separator"
+//usage:     "\n       -f FILE         Read program from FILE"
+//usage:       IF_FEATURE_AWK_GNU_EXTENSIONS(
+//usage:     "\n       -e AWK_PROGRAM"
+//usage:       )
 
 #include "libbb.h"
 #include "xregex.h"
 /* This is a NOEXEC applet. Be very careful! */
 
 
+/* If you comment out one of these below, it will be #defined later
+ * to perform debug printfs to stderr: */
+#define debug_printf_walker(...)  do {} while (0)
+#define debug_printf_eval(...)  do {} while (0)
+#define debug_printf_parse(...)  do {} while (0)
+
+#ifndef debug_printf_walker
+# define debug_printf_walker(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+#ifndef debug_printf_eval
+# define debug_printf_eval(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+#ifndef debug_printf_parse
+# define debug_printf_parse(...) (fprintf(stderr, __VA_ARGS__))
+#endif
+
+
+/* "+": stop on first non-option:
+ * $ awk 'BEGIN { for(i=1; i<ARGC; ++i) { print i ": " ARGV[i] }}' -argz
+ * 1: -argz
+ */
+#define OPTSTR_AWK "+" \
+       "F:v:*f:*" \
+       IF_FEATURE_AWK_GNU_EXTENSIONS("e:*") \
+       "W:"
+enum {
+       OPTBIT_F,       /* define field separator */
+       OPTBIT_v,       /* define variable */
+       OPTBIT_f,       /* pull in awk program from file */
+       IF_FEATURE_AWK_GNU_EXTENSIONS(OPTBIT_e,) /* -e AWK_PROGRAM */
+       OPTBIT_W,       /* -W ignored */
+       OPT_F = 1 << OPTBIT_F,
+       OPT_v = 1 << OPTBIT_v,
+       OPT_f = 1 << OPTBIT_f,
+       OPT_e = IF_FEATURE_AWK_GNU_EXTENSIONS((1 << OPTBIT_e)) + 0,
+       OPT_W = 1 << OPTBIT_W
+};
+
 #define        MAXVARFMT       240
 #define        MINNVBLOCK      64
 
 /* these flags are static, don't change them when value is changed */
 #define        VF_DONTTOUCH    (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY)
 
+typedef struct walker_list {
+       char *end;
+       char *cur;
+       struct walker_list *prev;
+       char wbuf[1];
+} walker_list;
+
 /* Variable */
 typedef struct var_s {
        unsigned type;            /* flags */
@@ -41,7 +124,7 @@ typedef struct var_s {
                int aidx;               /* func arg idx (for compilation stage) */
                struct xhash_s *array;  /* array ptr */
                struct var_s *parent;   /* for func args, ptr to actual parameter */
-               char **walker;          /* list of array elements (for..in) */
+               walker_list *walker;    /* list of array elements (for..in) */
        } x;
 } var;
 
@@ -93,15 +176,14 @@ typedef struct node_s {
        union {
                struct node_s *n;
                var *v;
-               int i;
-               char *s;
+               int aidx;
+               char *new_progname;
                regex_t *re;
        } l;
        union {
                struct node_s *n;
                regex_t *ire;
                func *f;
-               int argno;
        } r;
        union {
                struct node_s *n;
@@ -114,7 +196,7 @@ typedef struct nvblock_s {
        var *pos;
        struct nvblock_s *prev;
        struct nvblock_s *next;
-       var nv[0];
+       var nv[];
 } nvblock;
 
 typedef struct tsplitter_s {
@@ -124,7 +206,7 @@ typedef struct tsplitter_s {
 
 /* simple token classes */
 /* Order and hex values are very important!!!  See next_token() */
-#define        TC_SEQSTART      1                              /* ( */
+#define        TC_SEQSTART     (1 << 0)                /* ( */
 #define        TC_SEQTERM      (1 << 1)                /* ) */
 #define        TC_REGEXP       (1 << 2)                /* /.../ */
 #define        TC_OUTRDR       (1 << 3)                /* | > >> */
@@ -144,31 +226,39 @@ typedef struct tsplitter_s {
 #define        TC_WHILE        (1 << 17)
 #define        TC_ELSE         (1 << 18)
 #define        TC_BUILTIN      (1 << 19)
-#define        TC_GETLINE      (1 << 20)
-#define        TC_FUNCDECL     (1 << 21)               /* `function' `func' */
-#define        TC_BEGIN        (1 << 22)
-#define        TC_END          (1 << 23)
-#define        TC_EOF          (1 << 24)
-#define        TC_VARIABLE     (1 << 25)
-#define        TC_ARRAY        (1 << 26)
-#define        TC_FUNCTION     (1 << 27)
-#define        TC_STRING       (1 << 28)
-#define        TC_NUMBER       (1 << 29)
+/* This costs ~50 bytes of code.
+ * A separate class to support deprecated "length" form. If we don't need that
+ * (i.e. if we demand that only "length()" with () is valid), then TC_LENGTH
+ * can be merged with TC_BUILTIN:
+ */
+#define        TC_LENGTH       (1 << 20)
+#define        TC_GETLINE      (1 << 21)
+#define        TC_FUNCDECL     (1 << 22)               /* 'function' 'func' */
+#define        TC_BEGIN        (1 << 23)
+#define        TC_END          (1 << 24)
+#define        TC_EOF          (1 << 25)
+#define        TC_VARIABLE     (1 << 26)
+#define        TC_ARRAY        (1 << 27)
+#define        TC_FUNCTION     (1 << 28)
+#define        TC_STRING       (1 << 29)
+#define        TC_NUMBER       (1 << 30)
 
 #define        TC_UOPPRE  (TC_UOPPRE1 | TC_UOPPRE2)
 
 /* combined token classes */
 #define        TC_BINOP   (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
-#define        TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
+//#define      TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
 #define        TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION \
-                   | TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER)
+                   | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
+                   | TC_SEQSTART | TC_STRING | TC_NUMBER)
 
 #define        TC_STATEMNT (TC_STATX | TC_WHILE)
 #define        TC_OPTERM  (TC_SEMICOL | TC_NEWLINE)
 
 /* word tokens, cannot mean something else if not expected */
-#define        TC_WORD    (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN \
-                   | TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END)
+#define        TC_WORD    (TC_IN | TC_STATEMNT | TC_ELSE \
+                   | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
+                   | TC_FUNCDECL | TC_BEGIN | TC_END)
 
 /* discard newlines after these */
 #define        TC_NOTERM  (TC_COMMA | TC_GRPSTART | TC_GRPTERM \
@@ -211,6 +301,9 @@ typedef struct tsplitter_s {
  * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1,
  * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string
  */
+#undef P
+#undef PRIMASK
+#undef PRIMASK2
 #define P(x)      (x << 24)
 #define PRIMASK   0x7F000000
 #define PRIMASK2  0x7E000000
@@ -250,115 +343,107 @@ enum {
 
 /* builtins */
 enum {
-       B_a2,   B_ix,   B_ma,   B_sp,   B_ss,   B_ti,   B_lo,   B_up,
+       B_a2,   B_ix,   B_ma,   B_sp,   B_ss,   B_ti,   B_mt,   B_lo,   B_up,
        B_ge,   B_gs,   B_su,
        B_an,   B_co,   B_ls,   B_or,   B_rs,   B_xo,
 };
 
 /* tokens and their corresponding info values */
 
-#define        NTC     "\377"  /* switch to next token class (tc<<1) */
-#define        NTCC    '\377'
-
-#define        OC_B    OC_BUILTIN
+#define NTC     "\377"  /* switch to next token class (tc<<1) */
+#define NTCC    '\377'
 
 static const char tokenlist[] ALIGN1 =
-       "\1("       NTC
-       "\1)"       NTC
-       "\1/"       NTC                                 /* REGEXP */
-       "\2>>"      "\1>"       "\1|"       NTC         /* OUTRDR */
-       "\2++"      "\2--"      NTC                     /* UOPPOST */
-       "\2++"      "\2--"      "\1$"       NTC         /* UOPPRE1 */
-       "\2=="      "\1="       "\2+="      "\2-="      /* BINOPX */
-       "\2*="      "\2/="      "\2%="      "\2^="
-       "\1+"       "\1-"       "\3**="     "\2**"
-       "\1/"       "\1%"       "\1^"       "\1*"
-       "\2!="      "\2>="      "\2<="      "\1>"
-       "\1<"       "\2!~"      "\1~"       "\2&&"
-       "\2||"      "\1?"       "\1:"       NTC
-       "\2in"      NTC
-       "\1,"       NTC
-       "\1|"       NTC
-       "\1+"       "\1-"       "\1!"       NTC         /* UOPPRE2 */
-       "\1]"       NTC
-       "\1{"       NTC
-       "\1}"       NTC
-       "\1;"       NTC
-       "\1\n"      NTC
-       "\2if"      "\2do"      "\3for"     "\5break"   /* STATX */
-       "\10continue"           "\6delete"  "\5print"
-       "\6printf"  "\4next"    "\10nextfile"
-       "\6return"  "\4exit"    NTC
-       "\5while"   NTC
-       "\4else"    NTC
-
-       "\3and"     "\5compl"   "\6lshift"  "\2or"
-       "\6rshift"  "\3xor"
-       "\5close"   "\6system"  "\6fflush"  "\5atan2"   /* BUILTIN */
-       "\3cos"     "\3exp"     "\3int"     "\3log"
-       "\4rand"    "\3sin"     "\4sqrt"    "\5srand"
-       "\6gensub"  "\4gsub"    "\5index"   "\6length"
-       "\5match"   "\5split"   "\7sprintf" "\3sub"
-       "\6substr"  "\7systime" "\10strftime"
-       "\7tolower" "\7toupper" NTC
-       "\7getline" NTC
-       "\4func"    "\10function"   NTC
-       "\5BEGIN"   NTC
-       "\3END"     "\0"
+       "\1("         NTC                                   /* TC_SEQSTART */
+       "\1)"         NTC                                   /* TC_SEQTERM */
+       "\1/"         NTC                                   /* TC_REGEXP */
+       "\2>>"        "\1>"         "\1|"       NTC         /* TC_OUTRDR */
+       "\2++"        "\2--"        NTC                     /* TC_UOPPOST */
+       "\2++"        "\2--"        "\1$"       NTC         /* TC_UOPPRE1 */
+       "\2=="        "\1="         "\2+="      "\2-="      /* TC_BINOPX */
+       "\2*="        "\2/="        "\2%="      "\2^="
+       "\1+"         "\1-"         "\3**="     "\2**"
+       "\1/"         "\1%"         "\1^"       "\1*"
+       "\2!="        "\2>="        "\2<="      "\1>"
+       "\1<"         "\2!~"        "\1~"       "\2&&"
+       "\2||"        "\1?"         "\1:"       NTC
+       "\2in"        NTC                                   /* TC_IN */
+       "\1,"         NTC                                   /* TC_COMMA */
+       "\1|"         NTC                                   /* TC_PIPE */
+       "\1+"         "\1-"         "\1!"       NTC         /* TC_UOPPRE2 */
+       "\1]"         NTC                                   /* TC_ARRTERM */
+       "\1{"         NTC                                   /* TC_GRPSTART */
+       "\1}"         NTC                                   /* TC_GRPTERM */
+       "\1;"         NTC                                   /* TC_SEMICOL */
+       "\1\n"        NTC                                   /* TC_NEWLINE */
+       "\2if"        "\2do"        "\3for"     "\5break"   /* TC_STATX */
+       "\10continue" "\6delete"    "\5print"
+       "\6printf"    "\4next"      "\10nextfile"
+       "\6return"    "\4exit"      NTC
+       "\5while"     NTC                                   /* TC_WHILE */
+       "\4else"      NTC                                   /* TC_ELSE */
+       "\3and"       "\5compl"     "\6lshift"  "\2or"      /* TC_BUILTIN */
+       "\6rshift"    "\3xor"
+       "\5close"     "\6system"    "\6fflush"  "\5atan2"
+       "\3cos"       "\3exp"       "\3int"     "\3log"
+       "\4rand"      "\3sin"       "\4sqrt"    "\5srand"
+       "\6gensub"    "\4gsub"      "\5index"   /* "\6length" was here */
+       "\5match"     "\5split"     "\7sprintf" "\3sub"
+       "\6substr"    "\7systime"   "\10strftime" "\6mktime"
+       "\7tolower"   "\7toupper"   NTC
+       "\6length"    NTC                                   /* TC_LENGTH */
+       "\7getline"   NTC                                   /* TC_GETLINE */
+       "\4func"      "\10function" NTC                     /* TC_FUNCDECL */
+       "\5BEGIN"     NTC                                   /* TC_BEGIN */
+       "\3END"                                             /* TC_END */
+       /* compiler adds trailing "\0" */
        ;
 
+#define OC_B  OC_BUILTIN
+
 static const uint32_t tokeninfo[] = {
        0,
        0,
        OC_REGEXP,
-       xS|'a',     xS|'w',     xS|'|',
-       OC_UNARY|xV|P(9)|'p',       OC_UNARY|xV|P(9)|'m',
-       OC_UNARY|xV|P(9)|'P',       OC_UNARY|xV|P(9)|'M',
-           OC_FIELD|xV|P(5),
-       OC_COMPARE|VV|P(39)|5,      OC_MOVE|VV|P(74),
-           OC_REPLACE|NV|P(74)|'+',    OC_REPLACE|NV|P(74)|'-',
-       OC_REPLACE|NV|P(74)|'*',    OC_REPLACE|NV|P(74)|'/',
-           OC_REPLACE|NV|P(74)|'%',    OC_REPLACE|NV|P(74)|'&',
-       OC_BINARY|NV|P(29)|'+',     OC_BINARY|NV|P(29)|'-',
-           OC_REPLACE|NV|P(74)|'&',    OC_BINARY|NV|P(15)|'&',
-       OC_BINARY|NV|P(25)|'/',     OC_BINARY|NV|P(25)|'%',
-           OC_BINARY|NV|P(15)|'&',     OC_BINARY|NV|P(25)|'*',
-       OC_COMPARE|VV|P(39)|4,      OC_COMPARE|VV|P(39)|3,
-           OC_COMPARE|VV|P(39)|0,      OC_COMPARE|VV|P(39)|1,
-       OC_COMPARE|VV|P(39)|2,      OC_MATCH|Sx|P(45)|'!',
-           OC_MATCH|Sx|P(45)|'~',      OC_LAND|Vx|P(55),
-       OC_LOR|Vx|P(59),            OC_TERNARY|Vx|P(64)|'?',
-           OC_COLON|xx|P(67)|':',
-       OC_IN|SV|P(49),
+       xS|'a',                  xS|'w',                  xS|'|',
+       OC_UNARY|xV|P(9)|'p',    OC_UNARY|xV|P(9)|'m',
+       OC_UNARY|xV|P(9)|'P',    OC_UNARY|xV|P(9)|'M',    OC_FIELD|xV|P(5),
+       OC_COMPARE|VV|P(39)|5,   OC_MOVE|VV|P(74),        OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
+       OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
+       OC_BINARY|NV|P(29)|'+',  OC_BINARY|NV|P(29)|'-',  OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
+       OC_BINARY|NV|P(25)|'/',  OC_BINARY|NV|P(25)|'%',  OC_BINARY|NV|P(15)|'&',  OC_BINARY|NV|P(25)|'*',
+       OC_COMPARE|VV|P(39)|4,   OC_COMPARE|VV|P(39)|3,   OC_COMPARE|VV|P(39)|0,   OC_COMPARE|VV|P(39)|1,
+       OC_COMPARE|VV|P(39)|2,   OC_MATCH|Sx|P(45)|'!',   OC_MATCH|Sx|P(45)|'~',   OC_LAND|Vx|P(55),
+       OC_LOR|Vx|P(59),         OC_TERNARY|Vx|P(64)|'?', OC_COLON|xx|P(67)|':',
+       OC_IN|SV|P(49), /* TC_IN */
        OC_COMMA|SS|P(80),
        OC_PGETLINE|SV|P(37),
-       OC_UNARY|xV|P(19)|'+',      OC_UNARY|xV|P(19)|'-',
-           OC_UNARY|xV|P(19)|'!',
-       0,
+       OC_UNARY|xV|P(19)|'+',   OC_UNARY|xV|P(19)|'-',   OC_UNARY|xV|P(19)|'!',
+       0, /* ] */
        0,
        0,
        0,
-       0,
-       ST_IF,          ST_DO,          ST_FOR,         OC_BREAK,
-       OC_CONTINUE,                    OC_DELETE|Vx,   OC_PRINT,
-       OC_PRINTF,      OC_NEXT,        OC_NEXTFILE,
-       OC_RETURN|Vx,   OC_EXIT|Nx,
+       0, /* \n */
+       ST_IF,        ST_DO,        ST_FOR,      OC_BREAK,
+       OC_CONTINUE,  OC_DELETE|Vx, OC_PRINT,
+       OC_PRINTF,    OC_NEXT,      OC_NEXTFILE,
+       OC_RETURN|Vx, OC_EXIT|Nx,
        ST_WHILE,
-       0,
-
+       0, /* else */
        OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83),
        OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83),
        OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83),
        OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
        OC_FBLTIN|F_rn,    OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
-       OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le,
+       OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), /* OC_FBLTIN|Sx|F_le, was here */
        OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF,        OC_B|B_su|P(0xb6),
-       OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti,    OC_B|B_ti|P(0x0b),
+       OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti,    OC_B|B_ti|P(0x0b), OC_B|B_mt|P(0x0b),
        OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
+       OC_FBLTIN|Sx|F_le, /* TC_LENGTH */
        OC_GETLINE|SV|P(0),
-       0,      0,
+       0,                 0,
        0,
-       0
+       0 /* TC_END */
 };
 
 /* internal variable names and their initial values       */
@@ -366,25 +451,22 @@ static const uint32_t tokeninfo[] = {
 enum {
        CONVFMT,    OFMT,       FS,         OFS,
        ORS,        RS,         RT,         FILENAME,
-       SUBSEP,     ARGIND,     ARGC,       ARGV,
-       ERRNO,      FNR,
-       NR,         NF,         IGNORECASE,
-       ENVIRON,    F0,         NUM_INTERNAL_VARS
+       SUBSEP,     F0,         ARGIND,     ARGC,
+       ARGV,       ERRNO,      FNR,        NR,
+       NF,         IGNORECASE, ENVIRON,    NUM_INTERNAL_VARS
 };
 
 static const char vNames[] ALIGN1 =
        "CONVFMT\0" "OFMT\0"    "FS\0*"     "OFS\0"
        "ORS\0"     "RS\0*"     "RT\0"      "FILENAME\0"
-       "SUBSEP\0"  "ARGIND\0"  "ARGC\0"    "ARGV\0"
-       "ERRNO\0"   "FNR\0"
-       "NR\0"      "NF\0*"     "IGNORECASE\0*"
-       "ENVIRON\0" "$\0*"      "\0";
+       "SUBSEP\0"  "$\0*"      "ARGIND\0"  "ARGC\0"
+       "ARGV\0"    "ERRNO\0"   "FNR\0"     "NR\0"
+       "NF\0*"     "IGNORECASE\0*" "ENVIRON\0" "\0";
 
 static const char vValues[] ALIGN1 =
        "%.6g\0"    "%.6g\0"    " \0"       " \0"
        "\n\0"      "\n\0"      "\0"        "\0"
-       "\034\0"
-       "\377";
+       "\034\0"    "\0"        "\377";
 
 /* hash size may grow to these values */
 #define FIRST_PRIME 61
@@ -392,8 +474,12 @@ static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 };
 
 
 /* Globals. Split in two parts so that first one is addressed
- * with (mostly short) negative offsets */
+ * with (mostly short) negative offsets.
+ * NB: it's unsafe to put members of type "double"
+ * into globals2 (gcc may fail to align them).
+ */
 struct globals {
+       double t_double;
        chain beginseq, mainseq, endseq;
        chain *seq;
        node *break_ptr, *continue_ptr;
@@ -412,13 +498,13 @@ struct globals {
        smallint nextrec;
        smallint nextfile;
        smallint is_f0_split;
+       smallint t_rollback;
 };
 struct globals2 {
        uint32_t t_info; /* often used */
        uint32_t t_tclass;
        char *t_string;
        int t_lineno;
-       int t_rollback;
 
        var *intvar[NUM_INTERNAL_VARS]; /* often used */
 
@@ -442,16 +528,16 @@ struct globals2 {
        tsplitter exec_builtin__tspl;
 
        /* biggest and least used members go last */
-       double t_double;
        tsplitter fsplitter, rsplitter;
 };
 #define G1 (ptr_to_globals[-1])
 #define G (*(struct globals2 *)ptr_to_globals)
 /* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */
-/* char G1size[sizeof(G1)]; - 0x6c */
-/* char Gsize[sizeof(G)]; - 0x1cc */
+/*char G1size[sizeof(G1)]; - 0x74 */
+/*char Gsize[sizeof(G)]; - 0x1c4 */
 /* Trying to keep most of members accessible with short offsets: */
-/* char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+/*char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+#define t_double     (G1.t_double    )
 #define beginseq     (G1.beginseq    )
 #define mainseq      (G1.mainseq     )
 #define endseq       (G1.endseq      )
@@ -476,17 +562,16 @@ struct globals2 {
 #define nextrec      (G1.nextrec     )
 #define nextfile     (G1.nextfile    )
 #define is_f0_split  (G1.is_f0_split )
+#define t_rollback   (G1.t_rollback  )
 #define t_info       (G.t_info      )
 #define t_tclass     (G.t_tclass    )
 #define t_string     (G.t_string    )
-#define t_double     (G.t_double    )
 #define t_lineno     (G.t_lineno    )
-#define t_rollback   (G.t_rollback  )
 #define intvar       (G.intvar      )
 #define fsplitter    (G.fsplitter   )
 #define rsplitter    (G.rsplitter   )
 #define INIT_G() do { \
-       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G1) + sizeof(G)) + sizeof(G1)); \
+       SET_PTR_TO_GLOBALS((char*)xzalloc(sizeof(G1)+sizeof(G)) + sizeof(G1)); \
        G.next_token__ltclass = TC_OPTERM; \
        G.evaluate__seed = 1; \
 } while (0)
@@ -512,17 +597,16 @@ static const char EMSG_TOO_FEW_ARGS[] ALIGN1 = "Too few arguments for builtin";
 static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array";
 static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error";
 static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
-#if !ENABLE_FEATURE_AWK_MATH
 static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
-#endif
+static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field";
 
-static void zero_out_var(var * vp)
+static void zero_out_var(var *vp)
 {
        memset(vp, 0, sizeof(*vp));
 }
 
-static void syntax_error(const char *const message) NORETURN;
-static void syntax_error(const char *const message)
+static void syntax_error(const char *message) NORETURN;
+static void syntax_error(const char *message)
 {
        bb_error_msg_and_die("%s:%i: %s", g_progname, g_lineno, message);
 }
@@ -533,7 +617,8 @@ static unsigned hashidx(const char *name)
 {
        unsigned idx = 0;
 
-       while (*name) idx = *name++ + (idx << 6) - idx;
+       while (*name)
+               idx = *name++ + (idx << 6) - idx;
        return idx;
 }
 
@@ -542,9 +627,9 @@ static xhash *hash_init(void)
 {
        xhash *newhash;
 
-       newhash = xzalloc(sizeof(xhash));
+       newhash = xzalloc(sizeof(*newhash));
        newhash->csize = FIRST_PRIME;
-       newhash->items = xzalloc(newhash->csize * sizeof(hash_item *));
+       newhash->items = xzalloc(FIRST_PRIME * sizeof(newhash->items[0]));
 
        return newhash;
 }
@@ -554,10 +639,10 @@ static void *hash_search(xhash *hash, const char *name)
 {
        hash_item *hi;
 
-       hi = hash->items [ hashidx(name) % hash->csize ];
+       hi = hash->items[hashidx(name) % hash->csize];
        while (hi) {
                if (strcmp(hi->name, name) == 0)
-                       return &(hi->data);
+                       return &hi->data;
                hi = hi->next;
        }
        return NULL;
@@ -573,7 +658,7 @@ static void hash_rebuild(xhash *hash)
                return;
 
        newsize = PRIMES[hash->nprime++];
-       newitems = xzalloc(newsize * sizeof(hash_item *));
+       newitems = xzalloc(newsize * sizeof(newitems[0]));
 
        for (i = 0; i < hash->csize; i++) {
                hi = hash->items[i];
@@ -604,15 +689,15 @@ static void *hash_find(xhash *hash, const char *name)
                        hash_rebuild(hash);
 
                l = strlen(name) + 1;
-               hi = xzalloc(sizeof(hash_item) + l);
-               memcpy(hi->name, name, l);
+               hi = xzalloc(sizeof(*hi) + l);
+               strcpy(hi->name, name);
 
                idx = hashidx(name) % hash->csize;
                hi->next = hash->items[idx];
                hash->items[idx] = hi;
                hash->glen += l;
        }
-       return &(hi->data);
+       return &hi->data;
 }
 
 #define findvar(hash, name) ((var*)    hash_find((hash), (name)))
@@ -624,7 +709,7 @@ static void hash_remove(xhash *hash, const char *name)
 {
        hash_item *hi, **phi;
 
-       phi = &(hash->items[hashidx(name) % hash->csize]);
+       phi = &hash->items[hashidx(name) % hash->csize];
        while (*phi) {
                hi = *phi;
                if (strcmp(hi->name, name) == 0) {
@@ -634,16 +719,14 @@ static void hash_remove(xhash *hash, const char *name)
                        free(hi);
                        break;
                }
-               phi = &(hi->next);
+               phi = &hi->next;
        }
 }
 
 /* ------ some useful functions ------ */
 
-static void skip_spaces(char **s)
+static char *skip_spaces(char *p)
 {
-       char *p = *s;
-
        while (1) {
                if (*p == '\\' && p[1] == '\n') {
                        p++;
@@ -653,15 +736,15 @@ static void skip_spaces(char **s)
                }
                p++;
        }
-       *s = p;
+       return p;
 }
 
+/* returns old *s, advances *s past word and terminating NUL */
 static char *nextword(char **s)
 {
        char *p = *s;
-
-       while (*(*s)++) /* */;
-
+       while (*(*s)++ != '\0')
+               continue;
        return p;
 }
 
@@ -669,18 +752,59 @@ static char nextchar(char **s)
 {
        char c, *pps;
 
-       c = *((*s)++);
+       c = *(*s)++;
        pps = *s;
-       if (c == '\\') c = bb_process_escape_sequence((const char**)s);
-       if (c == '\\' && *s == pps) c = *((*s)++);
+       if (c == '\\')
+               c = bb_process_escape_sequence((const char**)s);
+       /* Example awk statement:
+        * s = "abc\"def"
+        * we must treat \" as "
+        */
+       if (c == '\\' && *s == pps) { /* unrecognized \z? */
+               c = *(*s); /* yes, fetch z */
+               if (c)
+                       (*s)++; /* advance unless z = NUL */
+       }
        return c;
 }
 
+/* TODO: merge with strcpy_and_process_escape_sequences()?
+ */
+static void unescape_string_in_place(char *s1)
+{
+       char *s = s1;
+       while ((*s1 = nextchar(&s)) != '\0')
+               s1++;
+}
+
 static ALWAYS_INLINE int isalnum_(int c)
 {
        return (isalnum(c) || c == '_');
 }
 
+static double my_strtod(char **pp)
+{
+       char *cp = *pp;
+       if (ENABLE_DESKTOP && cp[0] == '0') {
+               /* Might be hex or octal integer: 0x123abc or 07777 */
+               char c = (cp[1] | 0x20);
+               if (c == 'x' || isdigit(cp[1])) {
+                       unsigned long long ull = strtoull(cp, pp, 0);
+                       if (c == 'x')
+                               return ull;
+                       c = **pp;
+                       if (!isdigit(c) && c != '.')
+                               return ull;
+                       /* else: it may be a floating number. Examples:
+                        * 009.123 (*pp points to '9')
+                        * 000.123 (*pp points to '.')
+                        * fall through to strtod.
+                        */
+               }
+       }
+       return strtod(cp, pp);
+}
+
 /* -------- working with variables (set/get/copy/etc) -------- */
 
 static xhash *iamarray(var *v)
@@ -742,10 +866,10 @@ static var *setvar_s(var *v, const char *value)
        return setvar_p(v, (value && *value) ? xstrdup(value) : NULL);
 }
 
-/* same as setvar_s but set USER flag */
+/* same as setvar_s but sets USER flag */
 static var *setvar_u(var *v, const char *value)
 {
-       setvar_s(v, value);
+       v = setvar_s(v, value);
        v->type |= VF_USER;
        return v;
 }
@@ -753,11 +877,9 @@ static var *setvar_u(var *v, const char *value)
 /* set array element to user string */
 static void setari_u(var *a, int idx, const char *s)
 {
-       char sidx[sizeof(int)*3 + 1];
        var *v;
 
-       sprintf(sidx, "%d", idx);
-       v = findvar(iamarray(a), sidx);
+       v = findvar(iamarray(a), itoa(idx));
        setvar_u(v, s);
 }
 
@@ -790,25 +912,43 @@ static double getvar_i(var *v)
                v->number = 0;
                s = v->string;
                if (s && *s) {
-                       v->number = strtod(s, &s);
+                       debug_printf_eval("getvar_i: '%s'->", s);
+                       v->number = my_strtod(&s);
+                       debug_printf_eval("%f (s:'%s')\n", v->number, s);
                        if (v->type & VF_USER) {
-                               skip_spaces(&s);
+                               s = skip_spaces(s);
                                if (*s != '\0')
                                        v->type &= ~VF_USER;
                        }
                } else {
+                       debug_printf_eval("getvar_i: '%s'->zero\n", s);
                        v->type &= ~VF_USER;
                }
                v->type |= VF_CACHED;
        }
+       debug_printf_eval("getvar_i: %f\n", v->number);
        return v->number;
 }
 
+/* Used for operands of bitwise ops */
+static unsigned long getvar_i_int(var *v)
+{
+       double d = getvar_i(v);
+
+       /* Casting doubles to longs is undefined for values outside
+        * of target type range. Try to widen it as much as possible */
+       if (d >= 0)
+               return (unsigned long)d;
+       /* Why? Think about d == -4294967295.0 (assuming 32bit longs) */
+       return - (long) (unsigned long) (-d);
+}
+
 static var *copyvar(var *dest, const var *src)
 {
        if (dest != src) {
                clrvar(dest);
                dest->type |= (src->type & ~(VF_DONTTOUCH | VF_FSTR));
+               debug_printf_eval("copyvar: number:%f string:'%s'\n", src->number, src->string);
                dest->number = src->number;
                if (src->string)
                        dest->string = xstrdup(src->string);
@@ -819,7 +959,7 @@ static var *copyvar(var *dest, const var *src)
 
 static var *incvar(var *v)
 {
-       return setvar_i(v, getvar_i(v) + 1.);
+       return setvar_i(v, getvar_i(v) + 1.0);
 }
 
 /* return true if v is number or numeric string */
@@ -833,8 +973,8 @@ static int is_numeric(var *v)
 static int istrue(var *v)
 {
        if (is_numeric(v))
-               return (v->number == 0) ? 0 : 1;
-       return (v->string && *(v->string)) ? 1 : 0;
+               return (v->number != 0);
+       return (v->string && v->string[0]);
 }
 
 /* temporary variables allocator. Last allocated should be first freed */
@@ -846,7 +986,8 @@ static var *nvalloc(int n)
 
        while (g_cb) {
                pb = g_cb;
-               if ((g_cb->pos - g_cb->nv) + n <= g_cb->size) break;
+               if ((g_cb->pos - g_cb->nv) + n <= g_cb->size)
+                       break;
                g_cb = g_cb->next;
        }
 
@@ -857,7 +998,8 @@ static var *nvalloc(int n)
                g_cb->pos = g_cb->nv;
                g_cb->prev = pb;
                /*g_cb->next = NULL; - xzalloc did it */
-               if (pb) pb->next = g_cb;
+               if (pb)
+                       pb->next = g_cb;
        }
 
        v = r = g_cb->pos;
@@ -885,9 +1027,18 @@ static void nvfree(var *v)
                        free(p->x.array->items);
                        free(p->x.array);
                }
-               if (p->type & VF_WALK)
-                       free(p->x.walker);
-
+               if (p->type & VF_WALK) {
+                       walker_list *n;
+                       walker_list *w = p->x.walker;
+                       debug_printf_walker("nvfree: freeing walker @%p\n", &p->x.walker);
+                       p->x.walker = NULL;
+                       while (w) {
+                               n = w->prev;
+                               debug_printf_walker(" free(%p)\n", w);
+                               free(w);
+                               w = n;
+                       }
+               }
                clrvar(p);
        }
 
@@ -910,24 +1061,21 @@ static uint32_t next_token(uint32_t expected)
 /* Initialized to TC_OPTERM: */
 #define ltclass         (G.next_token__ltclass)
 
-       char *p, *pp, *s;
+       char *p, *s;
        const char *tl;
        uint32_t tc;
        const uint32_t *ti;
-       int l;
 
        if (t_rollback) {
                t_rollback = FALSE;
-
        } else if (concat_inserted) {
                concat_inserted = FALSE;
                t_tclass = save_tclass;
                t_info = save_info;
-
        } else {
                p = g_pos;
  readnext:
-               skip_spaces(&p);
+               p = skip_spaces(p);
                g_lineno = t_lineno;
                if (*p == '#')
                        while (*p != '\n' && *p != '\0')
@@ -938,19 +1086,22 @@ static uint32_t next_token(uint32_t expected)
 
                if (*p == '\0') {
                        tc = TC_EOF;
-
+                       debug_printf_parse("%s: token found: TC_EOF\n", __func__);
                } else if (*p == '\"') {
                        /* it's a string */
                        t_string = s = ++p;
                        while (*p != '\"') {
+                               char *pp;
                                if (*p == '\0' || *p == '\n')
                                        syntax_error(EMSG_UNEXP_EOS);
-                               *(s++) = nextchar(&p);
+                               pp = p;
+                               *s++ = nextchar(&pp);
+                               p = pp;
                        }
                        p++;
                        *s = '\0';
                        tc = TC_STRING;
-
+                       debug_printf_parse("%s: token found:'%s' TC_STRING\n", __func__, t_string);
                } else if ((expected & TC_REGEXP) && *p == '/') {
                        /* it's regexp */
                        t_string = s = ++p;
@@ -959,83 +1110,86 @@ static uint32_t next_token(uint32_t expected)
                                        syntax_error(EMSG_UNEXP_EOS);
                                *s = *p++;
                                if (*s++ == '\\') {
-                                       pp = p;
-                                       *(s-1) = bb_process_escape_sequence((const char **)&p);
-                                       if (*pp == '\\')
+                                       char *pp = p;
+                                       s[-1] = bb_process_escape_sequence((const char **)&pp);
+                                       if (*p == '\\')
                                                *s++ = '\\';
-                                       if (p == pp)
+                                       if (pp == p)
                                                *s++ = *p++;
+                                       else
+                                               p = pp;
                                }
                        }
                        p++;
                        *s = '\0';
                        tc = TC_REGEXP;
+                       debug_printf_parse("%s: token found:'%s' TC_REGEXP\n", __func__, t_string);
 
                } else if (*p == '.' || isdigit(*p)) {
                        /* it's a number */
-#if ENABLE_DESKTOP
-                       if (p[0] == '0' && (p[1] | 0x20) == 'x')
-                               t_double = strtoll(p, &p, 0);
-                       else
-#endif
-                               t_double = strtod(p, &p);
+                       char *pp = p;
+                       t_double = my_strtod(&pp);
+                       p = pp;
                        if (*p == '.')
                                syntax_error(EMSG_UNEXP_TOKEN);
                        tc = TC_NUMBER;
-
+                       debug_printf_parse("%s: token found:%f TC_NUMBER\n", __func__, t_double);
                } else {
                        /* search for something known */
                        tl = tokenlist;
                        tc = 0x00000001;
                        ti = tokeninfo;
                        while (*tl) {
-                               l = *(tl++);
-                               if (l == NTCC) {
+                               int l = (unsigned char) *tl++;
+                               if (l == (unsigned char) NTCC) {
                                        tc <<= 1;
                                        continue;
                                }
-                               /* if token class is expected, token
-                                * matches and it's not a longer word,
-                                * then this is what we are looking for
+                               /* if token class is expected,
+                                * token matches,
+                                * and it's not a longer word,
                                 */
                                if ((tc & (expected | TC_WORD | TC_NEWLINE))
-                                && *tl == *p && strncmp(p, tl, l) == 0
+                                && strncmp(p, tl, l) == 0
                                 && !((tc & TC_WORD) && isalnum_(p[l]))
                                ) {
+                                       /* then this is what we are looking for */
                                        t_info = *ti;
+                                       debug_printf_parse("%s: token found:'%.*s' t_info:%x\n", __func__, l, p, t_info);
                                        p += l;
-                                       break;
+                                       goto token_found;
                                }
                                ti++;
                                tl += l;
                        }
-
-                       if (!*tl) {
-                               /* it's a name (var/array/function),
-                                * otherwise it's something wrong
-                                */
-                               if (!isalnum_(*p))
-                                       syntax_error(EMSG_UNEXP_TOKEN);
-
-                               t_string = --p;
-                               while (isalnum_(*(++p))) {
-                                       *(p-1) = *p;
-                               }
-                               *(p-1) = '\0';
-                               tc = TC_VARIABLE;
-                               /* also consume whitespace between functionname and bracket */
-                               if (!(expected & TC_VARIABLE))
-                                       skip_spaces(&p);
-                               if (*p == '(') {
-                                       tc = TC_FUNCTION;
-                               } else {
-                                       if (*p == '[') {
-                                               p++;
-                                               tc = TC_ARRAY;
-                                       }
-                               }
+                       /* not a known token */
+
+                       /* is it a name? (var/array/function) */
+                       if (!isalnum_(*p))
+                               syntax_error(EMSG_UNEXP_TOKEN); /* no */
+                       /* yes */
+                       t_string = --p;
+                       while (isalnum_(*++p)) {
+                               p[-1] = *p;
+                       }
+                       p[-1] = '\0';
+                       tc = TC_VARIABLE;
+                       /* also consume whitespace between functionname and bracket */
+                       if (!(expected & TC_VARIABLE) || (expected & TC_ARRAY))
+                               p = skip_spaces(p);
+                       if (*p == '(') {
+                               tc = TC_FUNCTION;
+                               debug_printf_parse("%s: token found:'%s' TC_FUNCTION\n", __func__, t_string);
+                       } else {
+                               if (*p == '[') {
+                                       p++;
+                                       tc = TC_ARRAY;
+                                       debug_printf_parse("%s: token found:'%s' TC_ARRAY\n", __func__, t_string);
+                               } else
+                                       debug_printf_parse("%s: token found:'%s' TC_VARIABLE\n", __func__, t_string);
                        }
                }
+ token_found:
                g_pos = p;
 
                /* skipping newlines in some cases */
@@ -1056,9 +1210,10 @@ static uint32_t next_token(uint32_t expected)
        ltclass = t_tclass;
 
        /* Are we ready for this? */
-       if (!(ltclass & expected))
+       if (!(ltclass & expected)) {
                syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
                                EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
+       }
 
        return ltclass;
 #undef concat_inserted
@@ -1082,15 +1237,13 @@ static node *new_node(uint32_t info)
        return n;
 }
 
-static node *mk_re_node(const char *s, node *n, regex_t *re)
+static void mk_re_node(const char *s, node *n, regex_t *re)
 {
        n->info = OC_REGEXP;
        n->l.re = re;
        n->r.ire = re + 1;
        xregcomp(re, s, REG_EXTENDED);
        xregcomp(re + 1, s, REG_EXTENDED | REG_ICASE);
-
-       return n;
 }
 
 static node *condition(void)
@@ -1109,25 +1262,32 @@ static node *parse_expr(uint32_t iexp)
        uint32_t tc, xtc;
        var *v;
 
+       debug_printf_parse("%s(%x)\n", __func__, iexp);
+
        sn.info = PRIMASK;
        sn.r.n = glptr = NULL;
        xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp;
 
        while (!((tc = next_token(xtc)) & iexp)) {
+
                if (glptr && (t_info == (OC_COMPARE | VV | P(39) | 2))) {
                        /* input redirection (<) attached to glptr node */
+                       debug_printf_parse("%s: input redir\n", __func__);
                        cn = glptr->l.n = new_node(OC_CONCAT | SS | P(37));
                        cn->a.n = glptr;
                        xtc = TC_OPERAND | TC_UOPPRE;
                        glptr = NULL;
 
                } else if (tc & (TC_BINOP | TC_UOPPOST)) {
+                       debug_printf_parse("%s: TC_BINOP | TC_UOPPOST\n", __func__);
                        /* for binary and postfix-unary operators, jump back over
                         * previous operators with higher priority */
                        vn = cn;
-                       while ( ((t_info & PRIMASK) > (vn->a.n->info & PRIMASK2))
-                        || ((t_info == vn->info) && ((t_info & OPCLSMASK) == OC_COLON)) )
+                       while (((t_info & PRIMASK) > (vn->a.n->info & PRIMASK2))
+                           || ((t_info == vn->info) && ((t_info & OPCLSMASK) == OC_COLON))
+                       ) {
                                vn = vn->a.n;
+                       }
                        if ((t_info & OPCLSMASK) == OC_TERNARY)
                                t_info += P(6);
                        cn = vn->a.n->r.n = new_node(t_info);
@@ -1149,6 +1309,7 @@ static node *parse_expr(uint32_t iexp)
                        vn->a.n = cn;
 
                } else {
+                       debug_printf_parse("%s: other\n", __func__);
                        /* for operands and prefix-unary operators, attach them
                         * to last node */
                        vn = cn;
@@ -1156,17 +1317,19 @@ static node *parse_expr(uint32_t iexp)
                        cn->a.n = vn;
                        xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
                        if (tc & (TC_OPERAND | TC_REGEXP)) {
+                               debug_printf_parse("%s: TC_OPERAND | TC_REGEXP\n", __func__);
                                xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp;
                                /* one should be very careful with switch on tclass -
                                 * only simple tclasses should be used! */
                                switch (tc) {
                                case TC_VARIABLE:
                                case TC_ARRAY:
+                                       debug_printf_parse("%s: TC_VARIABLE | TC_ARRAY\n", __func__);
                                        cn->info = OC_VAR;
                                        v = hash_search(ahash, t_string);
                                        if (v != NULL) {
                                                cn->info = OC_FNARG;
-                                               cn->l.i = v->x.aidx;
+                                               cn->l.aidx = v->x.aidx;
                                        } else {
                                                cn->l.v = newvar(t_string);
                                        }
@@ -1178,6 +1341,7 @@ static node *parse_expr(uint32_t iexp)
 
                                case TC_NUMBER:
                                case TC_STRING:
+                                       debug_printf_parse("%s: TC_NUMBER | TC_STRING\n", __func__);
                                        cn->info = OC_VAR;
                                        v = cn->l.v = xzalloc(sizeof(var));
                                        if (tc & TC_NUMBER)
@@ -1187,32 +1351,51 @@ static node *parse_expr(uint32_t iexp)
                                        break;
 
                                case TC_REGEXP:
+                                       debug_printf_parse("%s: TC_REGEXP\n", __func__);
                                        mk_re_node(t_string, cn, xzalloc(sizeof(regex_t)*2));
                                        break;
 
                                case TC_FUNCTION:
+                                       debug_printf_parse("%s: TC_FUNCTION\n", __func__);
                                        cn->info = OC_FUNC;
                                        cn->r.f = newfunc(t_string);
                                        cn->l.n = condition();
                                        break;
 
                                case TC_SEQSTART:
+                                       debug_printf_parse("%s: TC_SEQSTART\n", __func__);
                                        cn = vn->r.n = parse_expr(TC_SEQTERM);
+                                       if (!cn)
+                                               syntax_error("Empty sequence");
                                        cn->a.n = vn;
                                        break;
 
                                case TC_GETLINE:
+                                       debug_printf_parse("%s: TC_GETLINE\n", __func__);
                                        glptr = cn;
                                        xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
                                        break;
 
                                case TC_BUILTIN:
+                                       debug_printf_parse("%s: TC_BUILTIN\n", __func__);
                                        cn->l.n = condition();
                                        break;
+
+                               case TC_LENGTH:
+                                       debug_printf_parse("%s: TC_LENGTH\n", __func__);
+                                       next_token(TC_SEQSTART | TC_OPTERM | TC_GRPTERM);
+                                       rollback_token();
+                                       if (t_tclass & TC_SEQSTART) {
+                                               /* It was a "(" token. Handle just like TC_BUILTIN */
+                                               cn->l.n = condition();
+                                       }
+                                       break;
                                }
                        }
                }
        }
+
+       debug_printf_parse("%s() returns %p\n", __func__, sn.r.n);
        return sn.r.n;
 }
 
@@ -1227,7 +1410,7 @@ static node *chain_node(uint32_t info)
        if (seq->programname != g_progname) {
                seq->programname = g_progname;
                n = chain_node(OC_NEWSOURCE);
-               n->l.s = xstrdup(g_progname);
+               n->l.new_progname = xstrdup(g_progname);
        }
 
        n = seq->last;
@@ -1281,17 +1464,25 @@ static void chain_group(void)
        } while (c & TC_NEWLINE);
 
        if (c & TC_GRPSTART) {
+               debug_printf_parse("%s: TC_GRPSTART\n", __func__);
                while (next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) {
-                       if (t_tclass & TC_NEWLINE) continue;
+                       debug_printf_parse("%s: !TC_GRPTERM\n", __func__);
+                       if (t_tclass & TC_NEWLINE)
+                               continue;
                        rollback_token();
                        chain_group();
                }
+               debug_printf_parse("%s: TC_GRPTERM\n", __func__);
        } else if (c & (TC_OPSEQ | TC_OPTERM)) {
+               debug_printf_parse("%s: TC_OPSEQ | TC_OPTERM\n", __func__);
                rollback_token();
                chain_expr(OC_EXEC | Vx);
-       } else {                                                /* TC_STATEMNT */
+       } else {
+               /* TC_STATEMNT */
+               debug_printf_parse("%s: TC_STATEMNT(?)\n", __func__);
                switch (t_info & OPCLSMASK) {
                case ST_IF:
+                       debug_printf_parse("%s: ST_IF\n", __func__);
                        n = chain_node(OC_BR | Vx);
                        n->l.n = condition();
                        chain_group();
@@ -1306,12 +1497,14 @@ static void chain_group(void)
                        break;
 
                case ST_WHILE:
+                       debug_printf_parse("%s: ST_WHILE\n", __func__);
                        n2 = condition();
                        n = chain_loop(NULL);
                        n->l.n = n2;
                        break;
 
                case ST_DO:
+                       debug_printf_parse("%s: ST_DO\n", __func__);
                        n2 = chain_node(OC_EXEC);
                        n = chain_loop(NULL);
                        n2->a.n = n->a.n;
@@ -1320,10 +1513,11 @@ static void chain_group(void)
                        break;
 
                case ST_FOR:
+                       debug_printf_parse("%s: ST_FOR\n", __func__);
                        next_token(TC_SEQSTART);
                        n2 = parse_expr(TC_SEMICOL | TC_SEQTERM);
                        if (t_tclass & TC_SEQTERM) {    /* for-in */
-                               if ((n2->info & OPCLSMASK) != OC_IN)
+                               if (!n2 || (n2->info & OPCLSMASK) != OC_IN)
                                        syntax_error(EMSG_UNEXP_TOKEN);
                                n = chain_node(OC_WALKINIT | VV);
                                n->l.n = n2->l.n;
@@ -1345,6 +1539,7 @@ static void chain_group(void)
 
                case OC_PRINT:
                case OC_PRINTF:
+                       debug_printf_parse("%s: OC_PRINT[F]\n", __func__);
                        n = chain_node(t_info);
                        n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM);
                        if (t_tclass & TC_OUTRDR) {
@@ -1356,17 +1551,22 @@ static void chain_group(void)
                        break;
 
                case OC_BREAK:
+                       debug_printf_parse("%s: OC_BREAK\n", __func__);
                        n = chain_node(OC_EXEC);
                        n->a.n = break_ptr;
+                       chain_expr(t_info);
                        break;
 
                case OC_CONTINUE:
+                       debug_printf_parse("%s: OC_CONTINUE\n", __func__);
                        n = chain_node(OC_EXEC);
                        n->a.n = continue_ptr;
+                       chain_expr(t_info);
                        break;
 
                /* delete, next, nextfile, return, exit */
                default:
+                       debug_printf_parse("%s: default\n", __func__);
                        chain_expr(t_info);
                }
        }
@@ -1384,19 +1584,22 @@ static void parse_program(char *p)
        while ((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
                        TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
 
-               if (tclass & TC_OPTERM)
+               if (tclass & TC_OPTERM) {
+                       debug_printf_parse("%s: TC_OPTERM\n", __func__);
                        continue;
+               }
 
                seq = &mainseq;
                if (tclass & TC_BEGIN) {
+                       debug_printf_parse("%s: TC_BEGIN\n", __func__);
                        seq = &beginseq;
                        chain_group();
-
                } else if (tclass & TC_END) {
+                       debug_printf_parse("%s: TC_END\n", __func__);
                        seq = &endseq;
                        chain_group();
-
                } else if (tclass & TC_FUNCDECL) {
+                       debug_printf_parse("%s: TC_FUNCDECL\n", __func__);
                        next_token(TC_FUNCTION);
                        g_pos++;
                        f = newfunc(t_string);
@@ -1404,32 +1607,35 @@ static void parse_program(char *p)
                        f->nargs = 0;
                        while (next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
                                v = findvar(ahash, t_string);
-                               v->x.aidx = (f->nargs)++;
+                               v->x.aidx = f->nargs++;
 
                                if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
                                        break;
                        }
-                       seq = &(f->body);
+                       seq = &f->body;
                        chain_group();
                        clear_array(ahash);
-
                } else if (tclass & TC_OPSEQ) {
+                       debug_printf_parse("%s: TC_OPSEQ\n", __func__);
                        rollback_token();
                        cn = chain_node(OC_TEST);
                        cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART);
                        if (t_tclass & TC_GRPSTART) {
+                               debug_printf_parse("%s: TC_GRPSTART\n", __func__);
                                rollback_token();
                                chain_group();
                        } else {
+                               debug_printf_parse("%s: !TC_GRPSTART\n", __func__);
                                chain_node(OC_PRINT);
                        }
                        cn->r.n = mainseq.last;
-
                } else /* if (tclass & TC_GRPSTART) */ {
+                       debug_printf_parse("%s: TC_GRPSTART(?)\n", __func__);
                        rollback_token();
                        chain_group();
                }
        }
+       debug_printf_parse("%s: TC_EOF\n", __func__);
 }
 
 
@@ -1447,10 +1653,10 @@ static node *mk_splitter(const char *s, tsplitter *spl)
                regfree(re);
                regfree(ire); // TODO: nuke ire, use re+1?
        }
-       if (strlen(s) > 1) {
+       if (s[0] && s[1]) { /* strlen(s) > 1 */
                mk_re_node(s, n, re);
        } else {
-               n->info = (uint32_t) *s;
+               n->info = (uint32_t) s[0];
        }
 
        return n;
@@ -1462,6 +1668,7 @@ static node *mk_splitter(const char *s, tsplitter *spl)
  */
 static regex_t *as_regex(node *op, regex_t *preg)
 {
+       int cflags;
        var *v;
        const char *s;
 
@@ -1470,18 +1677,32 @@ static regex_t *as_regex(node *op, regex_t *preg)
        }
        v = nvalloc(1);
        s = getvar_s(evaluate(op, v));
-       xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED);
+
+       cflags = icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED;
+       /* Testcase where REG_EXTENDED fails (unpaired '{'):
+        * echo Hi | awk 'gsub("@(samp|code|file)\{","");'
+        * gawk 3.1.5 eats this. We revert to ~REG_EXTENDED
+        * (maybe gsub is not supposed to use REG_EXTENDED?).
+        */
+       if (regcomp(preg, s, cflags)) {
+               cflags &= ~REG_EXTENDED;
+               xregcomp(preg, s, cflags);
+       }
        nvfree(v);
        return preg;
 }
 
-/* gradually increasing buffer */
-static void qrealloc(char **b, int n, int *size)
+/* gradually increasing buffer.
+ * note that we reallocate even if n == old_size,
+ * and thus there is at least one extra allocated byte.
+ */
+static char* qrealloc(char *b, int n, int *size)
 {
-       if (!*b || n >= *size) {
+       if (!b || n >= *size) {
                *size = n + (n>>1) + 80;
-               *b = xrealloc(*b, *size);
+               b = xrealloc(b, *size);
        }
+       return b;
 }
 
 /* resize field storage space */
@@ -1492,24 +1713,22 @@ static void fsrealloc(int size)
        if (size >= maxfields) {
                i = maxfields;
                maxfields = size + 16;
-               Fields = xrealloc(Fields, maxfields * sizeof(var));
+               Fields = xrealloc(Fields, maxfields * sizeof(Fields[0]));
                for (; i < maxfields; i++) {
                        Fields[i].type = VF_SPECIAL;
                        Fields[i].string = NULL;
                }
        }
-
-       if (size < nfields) {
-               for (i = size; i < nfields; i++) {
-                       clrvar(Fields + i);
-               }
+       /* if size < nfields, clear extra field variables */
+       for (i = size; i < nfields; i++) {
+               clrvar(Fields + i);
        }
        nfields = size;
 }
 
 static int awk_split(const char *s, node *spl, char **slist)
 {
-       int l, n = 0;
+       int l, n;
        char c[4];
        char *s1;
        regmatch_t pmatch[2]; // TODO: why [2]? [1] is enough...
@@ -1523,6 +1742,7 @@ static int awk_split(const char *s, node *spl, char **slist)
        if (*getvar_s(intvar[RS]) == '\0')
                c[2] = '\n';
 
+       n = 0;
        if ((spl->info & OPCLSMASK) == OC_REGEXP) {  /* regex split */
                if (!*s)
                        return n; /* "": zero fields */
@@ -1540,10 +1760,14 @@ static int awk_split(const char *s, node *spl, char **slist)
                                n++; /* we saw yet another delimiter */
                        } else {
                                pmatch[0].rm_eo = l;
-                               if (s[l]) pmatch[0].rm_eo++;
+                               if (s[l])
+                                       pmatch[0].rm_eo++;
                        }
                        memcpy(s1, s, l);
-                       s1[l] = '\0';
+                       /* make sure we remove *all* of the separator chars */
+                       do {
+                               s1[l] = '\0';
+                       } while (++l < pmatch[0].rm_eo);
                        nextword(&s1);
                        s += pmatch[0].rm_eo;
                } while (*s);
@@ -1562,8 +1786,9 @@ static int awk_split(const char *s, node *spl, char **slist)
                        c[0] = toupper(c[0]);
                        c[1] = tolower(c[1]);
                }
-               if (*s1) n++;
-               while ((s1 = strpbrk(s1, c))) {
+               if (*s1)
+                       n++;
+               while ((s1 = strpbrk(s1, c)) != NULL) {
                        *s1++ = '\0';
                        n++;
                }
@@ -1572,7 +1797,8 @@ static int awk_split(const char *s, node *spl, char **slist)
        /* space split */
        while (*s) {
                s = skip_whitespace(s);
-               if (!*s) break;
+               if (!*s)
+                       break;
                n++;
                while (*s && !isspace(*s))
                        *s1++ = *s++;
@@ -1637,7 +1863,7 @@ static void handle_special(var *v)
                                memcpy(b+len, sep, sl);
                                len += sl;
                        }
-                       qrealloc(&b, len+l+sl, &bsize);
+                       b = qrealloc(b, len+l+sl, &bsize);
                        memcpy(b+len, s, l);
                        len += l;
                }
@@ -1650,14 +1876,23 @@ static void handle_special(var *v)
                is_f0_split = FALSE;
 
        } else if (v == intvar[FS]) {
-               mk_splitter(getvar_s(v), &fsplitter);
+               /*
+                * The POSIX-2008 standard says that changing FS should have no effect on the
+                * current input line, but only on the next one. The language is:
+                *
+                * > Before the first reference to a field in the record is evaluated, the record
+                * > shall be split into fields, according to the rules in Regular Expressions,
+                * > using the value of FS that was current at the time the record was read.
+                *
+                * So, split up current line before assignment to FS:
+                */
+               split_f0();
 
+               mk_splitter(getvar_s(v), &fsplitter);
        } else if (v == intvar[RS]) {
                mk_splitter(getvar_s(v), &rsplitter);
-
        } else if (v == intvar[IGNORECASE]) {
                icase = istrue(v);
-
        } else {                                /* $n */
                n = getvar_i(intvar[NF]);
                setvar_i(intvar[NF], n > v-Fields ? n : v-Fields+1);
@@ -1682,21 +1917,28 @@ static node *nextarg(node **pn)
 
 static void hashwalk_init(var *v, xhash *array)
 {
-       char **w;
        hash_item *hi;
        unsigned i;
+       walker_list *w;
+       walker_list *prev_walker;
 
-       if (v->type & VF_WALK)
-               free(v->x.walker);
+       if (v->type & VF_WALK) {
+               prev_walker = v->x.walker;
+       } else {
+               v->type |= VF_WALK;
+               prev_walker = NULL;
+       }
+       debug_printf_walker("hashwalk_init: prev_walker:%p\n", prev_walker);
 
-       v->type |= VF_WALK;
-       w = v->x.walker = xzalloc(2 + 2*sizeof(char *) + array->glen);
-       w[0] = w[1] = (char *)(w + 2);
+       w = v->x.walker = xzalloc(sizeof(*w) + array->glen + 1); /* why + 1? */
+       debug_printf_walker(" walker@%p=%p\n", &v->x.walker, w);
+       w->cur = w->end = w->wbuf;
+       w->prev = prev_walker;
        for (i = 0; i < array->csize; i++) {
                hi = array->items[i];
                while (hi) {
-                       strcpy(*w, hi->name);
-                       nextword(w);
+                       strcpy(w->end, hi->name);
+                       nextword(&w->end);
                        hi = hi->next;
                }
        }
@@ -1704,13 +1946,18 @@ static void hashwalk_init(var *v, xhash *array)
 
 static int hashwalk_next(var *v)
 {
-       char **w;
+       walker_list *w = v->x.walker;
 
-       w = v->x.walker;
-       if (w[1] == w[0])
+       if (w->cur >= w->end) {
+               walker_list *prev_walker = w->prev;
+
+               debug_printf_walker("end of iteration, free(walker@%p:%p), prev_walker:%p\n", &v->x.walker, w, prev_walker);
+               free(w);
+               v->x.walker = prev_walker;
                return FALSE;
+       }
 
-       setvar_s(v, nextword(w+1));
+       setvar_s(v, nextword(&w->cur));
        return TRUE;
 }
 
@@ -1726,10 +1973,12 @@ static int awk_getline(rstream *rsm, var *v)
 {
        char *b;
        regmatch_t pmatch[2];
-       int a, p, pp=0, size;
+       int size, a, p, pp = 0;
        int fd, so, eo, r, rp;
        char c, *m, *s;
 
+       debug_printf_eval("entered %s()\n", __func__);
+
        /* we're using our own buffer since we need access to accumulating
         * characters
         */
@@ -1741,7 +1990,9 @@ static int awk_getline(rstream *rsm, var *v)
        c = (char) rsplitter.n.info;
        rp = 0;
 
-       if (!m) qrealloc(&m, 256, &size);
+       if (!m)
+               m = qrealloc(m, 256, &size);
+
        do {
                b = m + a;
                so = eo = p;
@@ -1757,7 +2008,8 @@ static int awk_getline(rstream *rsm, var *v)
                                }
                        } else if (c != '\0') {
                                s = strchr(b+pp, c);
-                               if (!s) s = memchr(b+pp, '\0', p - pp);
+                               if (!s)
+                                       s = memchr(b+pp, '\0', p - pp);
                                if (s) {
                                        so = eo = s-b;
                                        eo++;
@@ -1769,7 +2021,8 @@ static int awk_getline(rstream *rsm, var *v)
                                s = strstr(b+rp, "\n\n");
                                if (s) {
                                        so = eo = s-b;
-                                       while (b[eo] == '\n') eo++;
+                                       while (b[eo] == '\n')
+                                               eo++;
                                        if (b[eo] != '\0')
                                                break;
                                }
@@ -1777,12 +2030,12 @@ static int awk_getline(rstream *rsm, var *v)
                }
 
                if (a > 0) {
-                       memmove(m, (const void *)(m+a), p+1);
+                       memmove(m, m+a, p+1);
                        b = m;
                        a = 0;
                }
 
-               qrealloc(&m, a+p+128, &size);
+               m = qrealloc(m, a+p+128, &size);
                b = m + a;
                pp = p;
                p += safe_read(fd, b+p, size-p-1);
@@ -1812,6 +2065,8 @@ static int awk_getline(rstream *rsm, var *v)
        rsm->pos = p - eo;
        rsm->size = size;
 
+       debug_printf_eval("returning from %s(): %d\n", __func__, r);
+
        return r;
 }
 
@@ -1821,8 +2076,8 @@ static int fmt_num(char *b, int size, const char *format, double n, int int_as_i
        char c;
        const char *s = format;
 
-       if (int_as_int && n == (int)n) {
-               r = snprintf(b, size, "%d", (int)n);
+       if (int_as_int && n == (long long)n) {
+               r = snprintf(b, size, "%lld", (long long)n);
        } else {
                do { c = *s; } while (c && *++s);
                if (strchr("diouxX", c)) {
@@ -1836,7 +2091,6 @@ static int fmt_num(char *b, int size, const char *format, double n, int int_as_i
        return r;
 }
 
-
 /* formatted output into an allocated buffer, return ptr to buffer */
 static char *awk_printf(node *n)
 {
@@ -1853,7 +2107,7 @@ static char *awk_printf(node *n)
        i = 0;
        while (*f) {
                s = f;
-               while (*f && (*f != '%' || *(++f) == '%'))
+               while (*f && (*f != '%' || *++f == '%'))
                        f++;
                while (*f && !isalpha(*f)) {
                        if (*f == '*')
@@ -1862,9 +2116,10 @@ static char *awk_printf(node *n)
                }
 
                incr = (f - s) + MAXVARFMT;
-               qrealloc(&b, incr + i, &bsize);
+               b = qrealloc(b, incr + i, &bsize);
                c = *f;
-               if (c != '\0') f++;
+               if (c != '\0')
+                       f++;
                c1 = *f;
                *f = '\0';
                arg = evaluate(nextarg(&n), v);
@@ -1875,7 +2130,7 @@ static char *awk_printf(node *n)
                                        (char)getvar_i(arg) : *getvar_s(arg));
                } else if (c == 's') {
                        s1 = getvar_s(arg);
-                       qrealloc(&b, incr+i+strlen(s1), &bsize);
+                       b = qrealloc(b, incr+i+strlen(s1), &bsize);
                        i += sprintf(b+i, s, s1);
                } else {
                        i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE);
@@ -1883,95 +2138,147 @@ static char *awk_printf(node *n)
                *f = c1;
 
                /* if there was an error while sprintf, return value is negative */
-               if (i < j) i = j;
+               if (i < j)
+                       i = j;
        }
 
-       b = xrealloc(b, i + 1);
        free(fmt);
        nvfree(v);
+       b = xrealloc(b, i + 1);
        b[i] = '\0';
        return b;
 }
 
-/* common substitution routine
- * replace (nm) substring of (src) that match (n) with (repl), store
- * result into (dest), return number of substitutions. If nm=0, replace
- * all matches. If src or dst is NULL, use $0. If ex=TRUE, enable
- * subexpression matching (\1-\9)
+/* Common substitution routine.
+ * Replace (nm)'th substring of (src) that matches (rn) with (repl),
+ * store result into (dest), return number of substitutions.
+ * If nm = 0, replace all matches.
+ * If src or dst is NULL, use $0.
+ * If subexp != 0, enable subexpression matching (\1-\9).
  */
-static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int ex)
+static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest, int subexp)
 {
-       char *ds = NULL;
-       const char *s;
+       char *resbuf;
        const char *sp;
-       int c, i, j, di, rl, so, eo, nbs, n, dssize;
+       int match_no, residx, replen, resbufsize;
+       int regexec_flags;
        regmatch_t pmatch[10];
-       regex_t sreg, *re;
+       regex_t sreg, *regex;
+
+       resbuf = NULL;
+       residx = 0;
+       match_no = 0;
+       regexec_flags = 0;
+       regex = as_regex(rn, &sreg);
+       sp = getvar_s(src ? src : intvar[F0]);
+       replen = strlen(repl);
+       while (regexec(regex, sp, 10, pmatch, regexec_flags) == 0) {
+               int so = pmatch[0].rm_so;
+               int eo = pmatch[0].rm_eo;
+
+               //bb_error_msg("match %u: [%u,%u] '%s'%p", match_no+1, so, eo, sp,sp);
+               resbuf = qrealloc(resbuf, residx + eo + replen, &resbufsize);
+               memcpy(resbuf + residx, sp, eo);
+               residx += eo;
+               if (++match_no >= nm) {
+                       const char *s;
+                       int nbs;
 
-       re = as_regex(rn, &sreg);
-       if (!src) src = intvar[F0];
-       if (!dest) dest = intvar[F0];
-
-       i = di = 0;
-       sp = getvar_s(src);
-       rl = strlen(repl);
-       while (regexec(re, sp, 10, pmatch, sp==getvar_s(src) ? 0 : REG_NOTBOL) == 0) {
-               so = pmatch[0].rm_so;
-               eo = pmatch[0].rm_eo;
-
-               qrealloc(&ds, di + eo + rl, &dssize);
-               memcpy(ds + di, sp, eo);
-               di += eo;
-               if (++i >= nm) {
                        /* replace */
-                       di -= (eo - so);
+                       residx -= (eo - so);
                        nbs = 0;
                        for (s = repl; *s; s++) {
-                               ds[di++] = c = *s;
+                               char c = resbuf[residx++] = *s;
                                if (c == '\\') {
                                        nbs++;
                                        continue;
                                }
-                               if (c == '&' || (ex && c >= '0' && c <= '9')) {
-                                       di -= ((nbs + 3) >> 1);
+                               if (c == '&' || (subexp && c >= '0' && c <= '9')) {
+                                       int j;
+                                       residx -= ((nbs + 3) >> 1);
                                        j = 0;
                                        if (c != '&') {
                                                j = c - '0';
                                                nbs++;
                                        }
                                        if (nbs % 2) {
-                                               ds[di++] = c;
+                                               resbuf[residx++] = c;
                                        } else {
-                                               n = pmatch[j].rm_eo - pmatch[j].rm_so;
-                                               qrealloc(&ds, di + rl + n, &dssize);
-                                               memcpy(ds + di, sp + pmatch[j].rm_so, n);
-                                               di += n;
+                                               int n = pmatch[j].rm_eo - pmatch[j].rm_so;
+                                               resbuf = qrealloc(resbuf, residx + replen + n, &resbufsize);
+                                               memcpy(resbuf + residx, sp + pmatch[j].rm_so, n);
+                                               residx += n;
                                        }
                                }
                                nbs = 0;
                        }
                }
 
+               regexec_flags = REG_NOTBOL;
                sp += eo;
-               if (i == nm) break;
+               if (match_no == nm)
+                       break;
                if (eo == so) {
-                       ds[di] = *sp++;
-                       if (!ds[di++]) break;
+                       /* Empty match (e.g. "b*" will match anywhere).
+                        * Advance by one char. */
+//BUG (bug 1333):
+//gsub(/\<b*/,"") on "abc" will reach this point, advance to "bc"
+//... and will erroneously match "b" even though it is NOT at the word start.
+//we need REG_NOTBOW but it does not exist...
+//TODO: if EXTRA_COMPAT=y, use GNU matching and re_search,
+//it should be able to do it correctly.
+                       /* Subtle: this is safe only because
+                        * qrealloc allocated at least one extra byte */
+                       resbuf[residx] = *sp;
+                       if (*sp == '\0')
+                               goto ret;
+                       sp++;
+                       residx++;
                }
        }
 
-       qrealloc(&ds, di + strlen(sp), &dssize);
-       strcpy(ds + di, sp);
-       setvar_p(dest, ds);
-       if (re == &sreg) regfree(re);
-       return i;
+       resbuf = qrealloc(resbuf, residx + strlen(sp), &resbufsize);
+       strcpy(resbuf + residx, sp);
+ ret:
+       //bb_error_msg("end sp:'%s'%p", sp,sp);
+       setvar_p(dest ? dest : intvar[F0], resbuf);
+       if (regex == &sreg)
+               regfree(regex);
+       return match_no;
 }
 
-static var *exec_builtin(node *op, var *res)
+static NOINLINE int do_mktime(const char *ds)
+{
+       struct tm then;
+       int count;
+
+       /*memset(&then, 0, sizeof(then)); - not needed */
+       then.tm_isdst = -1; /* default is unknown */
+
+       /* manpage of mktime says these fields are ints,
+        * so we can sscanf stuff directly into them */
+       count = sscanf(ds, "%u %u %u %u %u %u %d",
+               &then.tm_year, &then.tm_mon, &then.tm_mday,
+               &then.tm_hour, &then.tm_min, &then.tm_sec,
+               &then.tm_isdst);
+
+       if (count < 6
+        || (unsigned)then.tm_mon < 1
+        || (unsigned)then.tm_year < 1900
+       ) {
+               return -1;
+       }
+
+       then.tm_mon -= 1;
+       then.tm_year -= 1900;
+
+       return mktime(&then);
+}
+
+static NOINLINE var *exec_builtin(node *op, var *res)
 {
 #define tspl (G.exec_builtin__tspl)
 
-       int (*to_xxx)(int);
        var *tv;
        node *an[4];
        var *av[4];
@@ -1982,7 +2289,6 @@ static var *exec_builtin(node *op, var *res)
        uint32_t isr, info;
        int nargs;
        time_t tt;
-       char *s, *s1;
        int i, l, ll, n;
 
        tv = nvalloc(4);
@@ -1992,8 +2298,10 @@ static var *exec_builtin(node *op, var *res)
        av[2] = av[3] = NULL;
        for (i = 0; i < 4 && op; i++) {
                an[i] = nextarg(&op);
-               if (isr & 0x09000000) av[i] = evaluate(an[i], &tv[i]);
-               if (isr & 0x08000000) as[i] = getvar_s(av[i]);
+               if (isr & 0x09000000)
+                       av[i] = evaluate(an[i], &tv[i]);
+               if (isr & 0x08000000)
+                       as[i] = getvar_s(av[i]);
                isr >>= 1;
        }
 
@@ -2001,17 +2309,19 @@ static var *exec_builtin(node *op, var *res)
        if ((uint32_t)nargs < (info >> 30))
                syntax_error(EMSG_TOO_FEW_ARGS);
 
-       switch (info & OPNMASK) {
+       info &= OPNMASK;
+       switch (info) {
 
        case B_a2:
-#if ENABLE_FEATURE_AWK_MATH
-               setvar_i(res, atan2(getvar_i(av[i]), getvar_i(av[1])));
-#else
-               syntax_error(EMSG_NO_MATH);
-#endif
+               if (ENABLE_FEATURE_AWK_LIBM)
+                       setvar_i(res, atan2(getvar_i(av[0]), getvar_i(av[1])));
+               else
+                       syntax_error(EMSG_NO_MATH);
                break;
 
-       case B_sp:
+       case B_sp: {
+               char *s, *s1;
+
                if (nargs > 2) {
                        spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ?
                                an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl);
@@ -2022,63 +2332,69 @@ static var *exec_builtin(node *op, var *res)
                n = awk_split(as[0], spl, &s);
                s1 = s;
                clear_array(iamarray(av[1]));
-               for (i=1; i<=n; i++)
-                       setari_u(av[1], i, nextword(&s1));
-               free(s);
+               for (i = 1; i <= n; i++)
+                       setari_u(av[1], i, nextword(&s));
+               free(s1);
                setvar_i(res, n);
                break;
+       }
+
+       case B_ss: {
+               char *s;
 
-       case B_ss:
                l = strlen(as[0]);
                i = getvar_i(av[1]) - 1;
-               if (i > l) i = l;
-               if (i < 0) i = 0;
+               if (i > l)
+                       i = l;
+               if (i < 0)
+                       i = 0;
                n = (nargs > 2) ? getvar_i(av[2]) : l-i;
-               if (n < 0) n = 0;
+               if (n < 0)
+                       n = 0;
                s = xstrndup(as[0]+i, n);
                setvar_p(res, s);
                break;
+       }
 
        /* Bitwise ops must assume that operands are unsigned. GNU Awk 3.1.5:
         * awk '{ print or(-1,1) }' gives "4.29497e+09", not "-2.xxxe+09" */
        case B_an:
-               setvar_i(res, (unsigned long)getvar_i(av[0]) & (unsigned long)getvar_i(av[1]));
+               setvar_i(res, getvar_i_int(av[0]) & getvar_i_int(av[1]));
                break;
 
        case B_co:
-               setvar_i(res, ~(unsigned long)getvar_i(av[0]));
+               setvar_i(res, ~getvar_i_int(av[0]));
                break;
 
        case B_ls:
-               setvar_i(res, (unsigned long)getvar_i(av[0]) << (unsigned long)getvar_i(av[1]));
+               setvar_i(res, getvar_i_int(av[0]) << getvar_i_int(av[1]));
                break;
 
        case B_or:
-               setvar_i(res, (unsigned long)getvar_i(av[0]) | (unsigned long)getvar_i(av[1]));
+               setvar_i(res, getvar_i_int(av[0]) | getvar_i_int(av[1]));
                break;
 
        case B_rs:
-               setvar_i(res, (unsigned long)getvar_i(av[0]) >> (unsigned long)getvar_i(av[1]));
+               setvar_i(res, getvar_i_int(av[0]) >> getvar_i_int(av[1]));
                break;
 
        case B_xo:
-               setvar_i(res, (unsigned long)getvar_i(av[0]) ^ (unsigned long)getvar_i(av[1]));
+               setvar_i(res, getvar_i_int(av[0]) ^ getvar_i_int(av[1]));
                break;
 
        case B_lo:
-               to_xxx = tolower;
-               goto lo_cont;
-
-       case B_up:
-               to_xxx = toupper;
- lo_cont:
+       case B_up: {
+               char *s, *s1;
                s1 = s = xstrdup(as[0]);
                while (*s1) {
-                       *s1 = (*to_xxx)(*s1);
+                       //*s1 = (info == B_up) ? toupper(*s1) : tolower(*s1);
+                       if ((unsigned char)((*s1 | 0x20) - 'a') <= ('z' - 'a'))
+                               *s1 = (info == B_up) ? (*s1 & 0xdf) : (*s1 | 0x20);
                        s1++;
                }
                setvar_p(res, s);
                break;
+       }
 
        case B_ix:
                n = 0;
@@ -2086,13 +2402,14 @@ static var *exec_builtin(node *op, var *res)
                l = strlen(as[0]) - ll;
                if (ll > 0 && l >= 0) {
                        if (!icase) {
-                               s = strstr(as[0], as[1]);
-                               if (s) n = (s - as[0]) + 1;
+                               char *s = strstr(as[0], as[1]);
+                               if (s)
+                                       n = (s - as[0]) + 1;
                        } else {
                                /* this piece of code is terribly slow and
                                 * really should be rewritten
                                 */
-                               for (i=0; i<=l; i++) {
+                               for (i = 0; i <= l; i++) {
                                        if (strncasecmp(as[0]+i, as[1], ll) == 0) {
                                                n = i+1;
                                                break;
@@ -2116,6 +2433,10 @@ static var *exec_builtin(node *op, var *res)
                setvar_s(res, g_buf);
                break;
 
+       case B_mt:
+               setvar_i(res, do_mktime(as[0]));
+               break;
+
        case B_ma:
                re = as_regex(an[1], &sreg);
                n = regexec(re, as[0], 1, pmatch, 0);
@@ -2129,7 +2450,8 @@ static var *exec_builtin(node *op, var *res)
                setvar_i(newvar("RSTART"), pmatch[0].rm_so);
                setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so);
                setvar_i(res, pmatch[0].rm_so);
-               if (re == &sreg) regfree(re);
+               if (re == &sreg)
+                       regfree(re);
                break;
 
        case B_ge:
@@ -2162,45 +2484,82 @@ static var *evaluate(node *op, var *res)
 #define fnargs (G.evaluate__fnargs)
 /* seed is initialized to 1 */
 #define seed   (G.evaluate__seed)
-#define        sreg   (G.evaluate__sreg)
+#define sreg   (G.evaluate__sreg)
 
-       node *op1;
        var *v1;
-       union {
-               var *v;
-               const char *s;
-               double d;
-               int i;
-       } L, R;
-       uint32_t opinfo;
-       int opn;
-       union {
-               char *s;
-               rstream *rsm;
-               FILE *F;
-               var *v;
-               regex_t *re;
-               uint32_t info;
-       } X;
 
        if (!op)
                return setvar_s(res, NULL);
 
+       debug_printf_eval("entered %s()\n", __func__);
+
        v1 = nvalloc(2);
 
        while (op) {
+               struct {
+                       var *v;
+                       const char *s;
+               } L = L; /* for compiler */
+               struct {
+                       var *v;
+                       const char *s;
+               } R = R;
+               double L_d = L_d;
+               uint32_t opinfo;
+               int opn;
+               node *op1;
+
                opinfo = op->info;
                opn = (opinfo & OPNMASK);
                g_lineno = op->lineno;
+               op1 = op->l.n;
+               debug_printf_eval("opinfo:%08x opn:%08x\n", opinfo, opn);
+
+               /* "delete" is special:
+                * "delete array[var--]" must evaluate index expr only once,
+                * must not evaluate it in "execute inevitable things" part.
+                */
+               if (XC(opinfo & OPCLSMASK) == XC(OC_DELETE)) {
+                       uint32_t info = op1->info & OPCLSMASK;
+                       var *v;
+
+                       debug_printf_eval("DELETE\n");
+                       if (info == OC_VAR) {
+                               v = op1->l.v;
+                       } else if (info == OC_FNARG) {
+                               v = &fnargs[op1->l.aidx];
+                       } else {
+                               syntax_error(EMSG_NOT_ARRAY);
+                       }
+                       if (op1->r.n) { /* array ref? */
+                               const char *s;
+                               s = getvar_s(evaluate(op1->r.n, v1));
+                               hash_remove(iamarray(v), s);
+                       } else {
+                               clear_array(iamarray(v));
+                       }
+                       goto next;
+               }
 
                /* execute inevitable things */
-               op1 = op->l.n;
-               if (opinfo & OF_RES1) X.v = L.v = evaluate(op1, v1);
-               if (opinfo & OF_RES2) R.v = evaluate(op->r.n, v1+1);
-               if (opinfo & OF_STR1) L.s = getvar_s(L.v);
-               if (opinfo & OF_STR2) R.s = getvar_s(R.v);
-               if (opinfo & OF_NUM1) L.d = getvar_i(L.v);
+               if (opinfo & OF_RES1)
+                       L.v = evaluate(op1, v1);
+               if (opinfo & OF_RES2)
+                       R.v = evaluate(op->r.n, v1+1);
+               if (opinfo & OF_STR1) {
+                       L.s = getvar_s(L.v);
+                       debug_printf_eval("L.s:'%s'\n", L.s);
+               }
+               if (opinfo & OF_STR2) {
+                       R.s = getvar_s(R.v);
+                       debug_printf_eval("R.s:'%s'\n", R.s);
+               }
+               if (opinfo & OF_NUM1) {
+                       L_d = getvar_i(L.v);
+                       debug_printf_eval("L_d:%f\n", L_d);
+               }
 
+               debug_printf_eval("switch(0x%x)\n", XC(opinfo & OPCLSMASK));
                switch (XC(opinfo & OPCLSMASK)) {
 
                /* -- iterative node type -- */
@@ -2213,13 +2572,12 @@ static var *evaluate(node *op, var *res)
                                        op->info |= OF_CHECKED;
                                        if (ptest(op1->r.n))
                                                op->info &= ~OF_CHECKED;
-
                                        op = op->a.n;
                                } else {
                                        op = op->r.n;
                                }
                        } else {
-                               op = (ptest(op1)) ? op->a.n : op->r.n;
+                               op = ptest(op1) ? op->a.n : op->r.n;
                        }
                        break;
 
@@ -2243,71 +2601,57 @@ static var *evaluate(node *op, var *res)
                        break;
 
                case XC( OC_PRINT ):
-               case XC( OC_PRINTF ):
-                       X.F = stdout;
+               case XC( OC_PRINTF ): {
+                       FILE *F = stdout;
+
                        if (op->r.n) {
-                               X.rsm = newfile(R.s);
-                               if (!X.rsm->F) {
+                               rstream *rsm = newfile(R.s);
+                               if (!rsm->F) {
                                        if (opn == '|') {
-                                               X.rsm->F = popen(R.s, "w");
-                                               if (X.rsm->F == NULL)
+                                               rsm->F = popen(R.s, "w");
+                                               if (rsm->F == NULL)
                                                        bb_perror_msg_and_die("popen");
-                                               X.rsm->is_pipe = 1;
+                                               rsm->is_pipe = 1;
                                        } else {
-                                               X.rsm->F = xfopen(R.s, opn=='w' ? "w" : "a");
+                                               rsm->F = xfopen(R.s, opn=='w' ? "w" : "a");
                                        }
                                }
-                               X.F = X.rsm->F;
+                               F = rsm->F;
                        }
 
                        if ((opinfo & OPCLSMASK) == OC_PRINT) {
                                if (!op1) {
-                                       fputs(getvar_s(intvar[F0]), X.F);
+                                       fputs(getvar_s(intvar[F0]), F);
                                } else {
                                        while (op1) {
-                                               L.v = evaluate(nextarg(&op1), v1);
-                                               if (L.v->type & VF_NUMBER) {
+                                               var *v = evaluate(nextarg(&op1), v1);
+                                               if (v->type & VF_NUMBER) {
                                                        fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[OFMT]),
-                                                                       getvar_i(L.v), TRUE);
-                                                       fputs(g_buf, X.F);
+                                                                       getvar_i(v), TRUE);
+                                                       fputs(g_buf, F);
                                                } else {
-                                                       fputs(getvar_s(L.v), X.F);
+                                                       fputs(getvar_s(v), F);
                                                }
 
-                                               if (op1) fputs(getvar_s(intvar[OFS]), X.F);
+                                               if (op1)
+                                                       fputs(getvar_s(intvar[OFS]), F);
                                        }
                                }
-                               fputs(getvar_s(intvar[ORS]), X.F);
+                               fputs(getvar_s(intvar[ORS]), F);
 
                        } else {        /* OC_PRINTF */
-                               L.s = awk_printf(op1);
-                               fputs(L.s, X.F);
-                               free((char*)L.s);
+                               char *s = awk_printf(op1);
+                               fputs(s, F);
+                               free(s);
                        }
-                       fflush(X.F);
+                       fflush(F);
                        break;
+               }
 
-               case XC( OC_DELETE ):
-                       X.info = op1->info & OPCLSMASK;
-                       if (X.info == OC_VAR) {
-                               R.v = op1->l.v;
-                       } else if (X.info == OC_FNARG) {
-                               R.v = &fnargs[op1->l.i];
-                       } else {
-                               syntax_error(EMSG_NOT_ARRAY);
-                       }
-
-                       if (op1->r.n) {
-                               clrvar(L.v);
-                               L.s = getvar_s(evaluate(op1->r.n, v1));
-                               hash_remove(iamarray(R.v), L.s);
-                       } else {
-                               clear_array(iamarray(R.v));
-                       }
-                       break;
+               /* case XC( OC_DELETE ): - moved to happen before arg evaluation */
 
                case XC( OC_NEWSOURCE ):
-                       g_progname = op->l.s;
+                       g_progname = op->l.new_progname;
                        break;
 
                case XC( OC_RETURN ):
@@ -2323,18 +2667,20 @@ static var *evaluate(node *op, var *res)
                        break;
 
                case XC( OC_EXIT ):
-                       awk_exit(L.d);
+                       awk_exit(L_d);
 
                /* -- recursive node type -- */
 
                case XC( OC_VAR ):
+                       debug_printf_eval("VAR\n");
                        L.v = op->l.v;
                        if (L.v == intvar[NF])
                                split_f0();
                        goto v_cont;
 
                case XC( OC_FNARG ):
-                       L.v = &fnargs[op->l.i];
+                       debug_printf_eval("FNARG[%d]\n", op->l.aidx);
+                       L.v = &fnargs[op->l.aidx];
  v_cont:
                        res = op->r.n ? findvar(iamarray(L.v), R.s) : L.v;
                        break;
@@ -2351,20 +2697,26 @@ static var *evaluate(node *op, var *res)
                case XC( OC_MATCH ):
                        op1 = op->r.n;
  re_cont:
-                       X.re = as_regex(op1, &sreg);
-                       R.i = regexec(X.re, L.s, 0, NULL, 0);
-                       if (X.re == &sreg) regfree(X.re);
-                       setvar_i(res, (R.i == 0 ? 1 : 0) ^ (opn == '!' ? 1 : 0));
+                       {
+                               regex_t *re = as_regex(op1, &sreg);
+                               int i = regexec(re, L.s, 0, NULL, 0);
+                               if (re == &sreg)
+                                       regfree(re);
+                               setvar_i(res, (i == 0) ^ (opn == '!'));
+                       }
                        break;
 
                case XC( OC_MOVE ):
+                       debug_printf_eval("MOVE\n");
                        /* if source is a temporary string, jusk relink it to dest */
-                       if (R.v == v1+1 && R.v->string) {
-                               res = setvar_p(L.v, R.v->string);
-                               R.v->string = NULL;
-                       } else {
+//Disabled: if R.v is numeric but happens to have cached R.v->string,
+//then L.v ends up being a string, which is wrong
+//                     if (R.v == v1+1 && R.v->string) {
+//                             res = setvar_p(L.v, R.v->string);
+//                             R.v->string = NULL;
+//                     } else {
                                res = copyvar(L.v, R.v);
-                       }
+//                     }
                        break;
 
                case XC( OC_TERNARY ):
@@ -2373,49 +2725,59 @@ static var *evaluate(node *op, var *res)
                        res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
                        break;
 
-               case XC( OC_FUNC ):
-                       if (!op->r.f->body.first)
+               case XC( OC_FUNC ): {
+                       var *vbeg, *v;
+                       const char *sv_progname;
+
+                       /* The body might be empty, still has to eval the args */
+                       if (!op->r.n->info && !op->r.f->body.first)
                                syntax_error(EMSG_UNDEF_FUNC);
 
-                       X.v = R.v = nvalloc(op->r.f->nargs+1);
+                       vbeg = v = nvalloc(op->r.f->nargs + 1);
                        while (op1) {
-                               L.v = evaluate(nextarg(&op1), v1);
-                               copyvar(R.v, L.v);
-                               R.v->type |= VF_CHILD;
-                               R.v->x.parent = L.v;
-                               if (++R.v - X.v >= op->r.f->nargs)
+                               var *arg = evaluate(nextarg(&op1), v1);
+                               copyvar(v, arg);
+                               v->type |= VF_CHILD;
+                               v->x.parent = arg;
+                               if (++v - vbeg >= op->r.f->nargs)
                                        break;
                        }
 
-                       R.v = fnargs;
-                       fnargs = X.v;
+                       v = fnargs;
+                       fnargs = vbeg;
+                       sv_progname = g_progname;
 
-                       L.s = g_progname;
                        res = evaluate(op->r.f->body.first, res);
-                       g_progname = L.s;
 
+                       g_progname = sv_progname;
                        nvfree(fnargs);
-                       fnargs = R.v;
+                       fnargs = v;
+
                        break;
+               }
 
                case XC( OC_GETLINE ):
-               case XC( OC_PGETLINE ):
+               case XC( OC_PGETLINE ): {
+                       rstream *rsm;
+                       int i;
+
                        if (op1) {
-                               X.rsm = newfile(L.s);
-                               if (!X.rsm->F) {
+                               rsm = newfile(L.s);
+                               if (!rsm->F) {
                                        if ((opinfo & OPCLSMASK) == OC_PGETLINE) {
-                                               X.rsm->F = popen(L.s, "r");
-                                               X.rsm->is_pipe = TRUE;
+                                               rsm->F = popen(L.s, "r");
+                                               rsm->is_pipe = TRUE;
                                        } else {
-                                               X.rsm->F = fopen_for_read(L.s);         /* not xfopen! */
+                                               rsm->F = fopen_for_read(L.s);  /* not xfopen! */
                                        }
                                }
                        } else {
-                               if (!iF) iF = next_input_file();
-                               X.rsm = iF;
+                               if (!iF)
+                                       iF = next_input_file();
+                               rsm = iF;
                        }
 
-                       if (!X.rsm->F) {
+                       if (!rsm || !rsm->F) {
                                setvar_i(intvar[ERRNO], errno);
                                setvar_i(res, -1);
                                break;
@@ -2424,105 +2786,129 @@ static var *evaluate(node *op, var *res)
                        if (!op->r.n)
                                R.v = intvar[F0];
 
-                       L.i = awk_getline(X.rsm, R.v);
-                       if (L.i > 0) {
-                               if (!op1) {
-                                       incvar(intvar[FNR]);
-                                       incvar(intvar[NR]);
-                               }
+                       i = awk_getline(rsm, R.v);
+                       if (i > 0 && !op1) {
+                               incvar(intvar[FNR]);
+                               incvar(intvar[NR]);
                        }
-                       setvar_i(res, L.i);
+                       setvar_i(res, i);
                        break;
+               }
 
                /* simple builtins */
-               case XC( OC_FBLTIN ):
-                       switch (opn) {
+               case XC( OC_FBLTIN ): {
+                       double R_d = R_d; /* for compiler */
 
+                       switch (opn) {
                        case F_in:
-                               R.d = (int)L.d;
+                               R_d = (long long)L_d;
                                break;
 
                        case F_rn:
-                               R.d = (double)rand() / (double)RAND_MAX;
+                               R_d = (double)rand() / (double)RAND_MAX;
                                break;
-#if ENABLE_FEATURE_AWK_MATH
+
                        case F_co:
-                               R.d = cos(L.d);
-                               break;
+                               if (ENABLE_FEATURE_AWK_LIBM) {
+                                       R_d = cos(L_d);
+                                       break;
+                               }
 
                        case F_ex:
-                               R.d = exp(L.d);
-                               break;
+                               if (ENABLE_FEATURE_AWK_LIBM) {
+                                       R_d = exp(L_d);
+                                       break;
+                               }
 
                        case F_lg:
-                               R.d = log(L.d);
-                               break;
+                               if (ENABLE_FEATURE_AWK_LIBM) {
+                                       R_d = log(L_d);
+                                       break;
+                               }
 
                        case F_si:
-                               R.d = sin(L.d);
-                               break;
+                               if (ENABLE_FEATURE_AWK_LIBM) {
+                                       R_d = sin(L_d);
+                                       break;
+                               }
 
                        case F_sq:
-                               R.d = sqrt(L.d);
-                               break;
-#else
-                       case F_co:
-                       case F_ex:
-                       case F_lg:
-                       case F_si:
-                       case F_sq:
+                               if (ENABLE_FEATURE_AWK_LIBM) {
+                                       R_d = sqrt(L_d);
+                                       break;
+                               }
+
                                syntax_error(EMSG_NO_MATH);
                                break;
-#endif
+
                        case F_sr:
-                               R.d = (double)seed;
-                               seed = op1 ? (unsigned)L.d : (unsigned)time(NULL);
+                               R_d = (double)seed;
+                               seed = op1 ? (unsigned)L_d : (unsigned)time(NULL);
                                srand(seed);
                                break;
 
                        case F_ti:
-                               R.d = time(NULL);
+                               R_d = time(NULL);
                                break;
 
                        case F_le:
-                               if (!op1)
+                               debug_printf_eval("length: L.s:'%s'\n", L.s);
+                               if (!op1) {
                                        L.s = getvar_s(intvar[F0]);
-                               R.d = strlen(L.s);
+                                       debug_printf_eval("length: L.s='%s'\n", L.s);
+                               }
+                               else if (L.v->type & VF_ARRAY) {
+                                       R_d = L.v->x.array->nel;
+                                       debug_printf_eval("length: array_len:%d\n", L.v->x.array->nel);
+                                       break;
+                               }
+                               R_d = strlen(L.s);
                                break;
 
                        case F_sy:
-                               fflush(NULL);
-                               R.d = (ENABLE_FEATURE_ALLOW_EXEC && L.s && *L.s)
+                               fflush_all();
+                               R_d = (ENABLE_FEATURE_ALLOW_EXEC && L.s && *L.s)
                                                ? (system(L.s) >> 8) : 0;
                                break;
 
                        case F_ff:
-                               if (!op1)
+                               if (!op1) {
                                        fflush(stdout);
-                               else {
-                                       if (L.s && *L.s) {
-                                               X.rsm = newfile(L.s);
-                                               fflush(X.rsm->F);
-                                       } else {
-                                               fflush(NULL);
-                                       }
+                               } else if (L.s && *L.s) {
+                                       rstream *rsm = newfile(L.s);
+                                       fflush(rsm->F);
+                               } else {
+                                       fflush_all();
                                }
                                break;
 
-                       case F_cl:
-                               X.rsm = (rstream *)hash_search(fdhash, L.s);
-                               if (X.rsm) {
-                                       R.i = X.rsm->is_pipe ? pclose(X.rsm->F) : fclose(X.rsm->F);
-                                       free(X.rsm->buffer);
+                       case F_cl: {
+                               rstream *rsm;
+                               int err = 0;
+                               rsm = (rstream *)hash_search(fdhash, L.s);
+                               debug_printf_eval("OC_FBLTIN F_cl rsm:%p\n", rsm);
+                               if (rsm) {
+                                       debug_printf_eval("OC_FBLTIN F_cl "
+                                               "rsm->is_pipe:%d, ->F:%p\n",
+                                               rsm->is_pipe, rsm->F);
+                                       /* Can be NULL if open failed. Example:
+                                        * getline line <"doesnt_exist";
+                                        * close("doesnt_exist"); <--- here rsm->F is NULL
+                                        */
+                                       if (rsm->F)
+                                               err = rsm->is_pipe ? pclose(rsm->F) : fclose(rsm->F);
+                                       free(rsm->buffer);
                                        hash_remove(fdhash, L.s);
                                }
-                               if (R.i != 0)
+                               if (err)
                                        setvar_i(intvar[ERRNO], errno);
-                               R.d = (double)R.i;
+                               R_d = (double)err;
                                break;
                        }
-                       setvar_i(res, R.d);
+                       } /* switch */
+                       setvar_i(res, R_d);
                        break;
+               }
 
                case XC( OC_BUILTIN ):
                        res = exec_builtin(op, res);
@@ -2532,60 +2918,60 @@ static var *evaluate(node *op, var *res)
                        setvar_p(res, awk_printf(op1));
                        break;
 
-               case XC( OC_UNARY ):
-                       X.v = R.v;
-                       L.d = R.d = getvar_i(R.v);
+               case XC( OC_UNARY ): {
+                       double Ld, R_d;
+
+                       Ld = R_d = getvar_i(R.v);
                        switch (opn) {
                        case 'P':
-                               L.d = ++R.d;
+                               Ld = ++R_d;
                                goto r_op_change;
                        case 'p':
-                               R.d++;
+                               R_d++;
                                goto r_op_change;
                        case 'M':
-                               L.d = --R.d;
+                               Ld = --R_d;
                                goto r_op_change;
                        case 'm':
-                               R.d--;
-                               goto r_op_change;
+                               R_d--;
+ r_op_change:
+                               setvar_i(R.v, R_d);
+                               break;
                        case '!':
-                               L.d = istrue(X.v) ? 0 : 1;
+                               Ld = !istrue(R.v);
                                break;
                        case '-':
-                               L.d = -R.d;
+                               Ld = -R_d;
                                break;
- r_op_change:
-                               setvar_i(X.v, R.d);
                        }
-                       setvar_i(res, L.d);
+                       setvar_i(res, Ld);
                        break;
+               }
 
-               case XC( OC_FIELD ):
-                       R.i = (int)getvar_i(R.v);
-                       if (R.i == 0) {
+               case XC( OC_FIELD ): {
+                       int i = (int)getvar_i(R.v);
+                       if (i < 0)
+                               syntax_error(EMSG_NEGATIVE_FIELD);
+                       if (i == 0) {
                                res = intvar[F0];
                        } else {
                                split_f0();
-                               if (R.i > nfields)
-                                       fsrealloc(R.i);
-                               res = &Fields[R.i - 1];
+                               if (i > nfields)
+                                       fsrealloc(i);
+                               res = &Fields[i - 1];
                        }
                        break;
+               }
 
                /* concatenation (" ") and index joining (",") */
                case XC( OC_CONCAT ):
-               case XC( OC_COMMA ):
-                       opn = strlen(L.s) + strlen(R.s) + 2;
-                       X.s = xmalloc(opn);
-                       strcpy(X.s, L.s);
-                       if ((opinfo & OPCLSMASK) == OC_COMMA) {
-                               L.s = getvar_s(intvar[SUBSEP]);
-                               X.s = xrealloc(X.s, opn + strlen(L.s));
-                               strcat(X.s, L.s);
-                       }
-                       strcat(X.s, R.s);
-                       setvar_p(res, X.s);
+               case XC( OC_COMMA ): {
+                       const char *sep = "";
+                       if ((opinfo & OPCLSMASK) == OC_COMMA)
+                               sep = getvar_s(intvar[SUBSEP]);
+                       setvar_p(res, xasprintf("%s%s%s", L.s, sep, R.s));
                        break;
+               }
 
                case XC( OC_LAND ):
                        setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0);
@@ -2596,70 +2982,81 @@ static var *evaluate(node *op, var *res)
                        break;
 
                case XC( OC_BINARY ):
-               case XC( OC_REPLACE ):
-                       R.d = getvar_i(R.v);
+               case XC( OC_REPLACE ): {
+                       double R_d = getvar_i(R.v);
+                       debug_printf_eval("BINARY/REPLACE: R_d:%f opn:%c\n", R_d, opn);
                        switch (opn) {
                        case '+':
-                               L.d += R.d;
+                               L_d += R_d;
                                break;
                        case '-':
-                               L.d -= R.d;
+                               L_d -= R_d;
                                break;
                        case '*':
-                               L.d *= R.d;
+                               L_d *= R_d;
                                break;
                        case '/':
-                               if (R.d == 0) syntax_error(EMSG_DIV_BY_ZERO);
-                               L.d /= R.d;
+                               if (R_d == 0)
+                                       syntax_error(EMSG_DIV_BY_ZERO);
+                               L_d /= R_d;
                                break;
                        case '&':
-#if ENABLE_FEATURE_AWK_MATH
-                               L.d = pow(L.d, R.d);
-#else
-                               syntax_error(EMSG_NO_MATH);
-#endif
+                               if (ENABLE_FEATURE_AWK_LIBM)
+                                       L_d = pow(L_d, R_d);
+                               else
+                                       syntax_error(EMSG_NO_MATH);
                                break;
                        case '%':
-                               if (R.d == 0) syntax_error(EMSG_DIV_BY_ZERO);
-                               L.d -= (int)(L.d / R.d) * R.d;
+                               if (R_d == 0)
+                                       syntax_error(EMSG_DIV_BY_ZERO);
+                               L_d -= (long long)(L_d / R_d) * R_d;
                                break;
                        }
-                       res = setvar_i(((opinfo & OPCLSMASK) == OC_BINARY) ? res : X.v, L.d);
+                       debug_printf_eval("BINARY/REPLACE result:%f\n", L_d);
+                       res = setvar_i(((opinfo & OPCLSMASK) == OC_BINARY) ? res : L.v, L_d);
                        break;
+               }
+
+               case XC( OC_COMPARE ): {
+                       int i = i; /* for compiler */
+                       double Ld;
 
-               case XC( OC_COMPARE ):
                        if (is_numeric(L.v) && is_numeric(R.v)) {
-                               L.d = getvar_i(L.v) - getvar_i(R.v);
+                               Ld = getvar_i(L.v) - getvar_i(R.v);
                        } else {
-                               L.s = getvar_s(L.v);
-                               R.s = getvar_s(R.v);
-                               L.d = icase ? strcasecmp(L.s, R.s) : strcmp(L.s, R.s);
+                               const char *l = getvar_s(L.v);
+                               const char *r = getvar_s(R.v);
+                               Ld = icase ? strcasecmp(l, r) : strcmp(l, r);
                        }
                        switch (opn & 0xfe) {
                        case 0:
-                               R.i = (L.d > 0);
+                               i = (Ld > 0);
                                break;
                        case 2:
-                               R.i = (L.d >= 0);
+                               i = (Ld >= 0);
                                break;
                        case 4:
-                               R.i = (L.d == 0);
+                               i = (Ld == 0);
                                break;
                        }
-                       setvar_i(res, (opn & 0x1 ? R.i : !R.i) ? 1 : 0);
+                       setvar_i(res, (i == 0) ^ (opn & 1));
                        break;
+               }
 
                default:
                        syntax_error(EMSG_POSSIBLE_ERROR);
-               }
+               } /* switch */
+ next:
                if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
                        op = op->a.n;
                if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
                        break;
                if (nextrec)
                        break;
-       }
+       } /* while (op) */
+
        nvfree(v1);
+       debug_printf_eval("returning from %s(): %p\n", __func__, res);
        return res;
 #undef fnargs
 #undef seed
@@ -2700,21 +3097,18 @@ static int awk_exit(int r)
  * otherwise return 0 */
 static int is_assignment(const char *expr)
 {
-       char *exprc, *s, *s0, *s1;
+       char *exprc, *val;
 
-       exprc = xstrdup(expr);
-       if (!isalnum_(*exprc) || (s = strchr(exprc, '=')) == NULL) {
-               free(exprc);
+       if (!isalnum_(*expr) || (val = strchr(expr, '=')) == NULL) {
                return FALSE;
        }
 
-       *(s++) = '\0';
-       s0 = s1 = s;
-       while (*s)
-               *(s1++) = nextchar(&s);
+       exprc = xstrdup(expr);
+       val = exprc + (val - expr);
+       *val++ = '\0';
 
-       *s1 = '\0';
-       setvar_u(newvar(exprc), s0);
+       unescape_string_in_place(val);
+       setvar_u(newvar(exprc), val);
        free(exprc);
        return TRUE;
 }
@@ -2725,26 +3119,29 @@ static rstream *next_input_file(void)
 #define rsm          (G.next_input_file__rsm)
 #define files_happen (G.next_input_file__files_happen)
 
-       FILE *F = NULL;
+       FILE *F;
        const char *fname, *ind;
 
-       if (rsm.F) fclose(rsm.F);
+       if (rsm.F)
+               fclose(rsm.F);
        rsm.F = NULL;
        rsm.pos = rsm.adv = 0;
 
-       do {
+       for (;;) {
                if (getvar_i(intvar[ARGIND])+1 >= getvar_i(intvar[ARGC])) {
                        if (files_happen)
                                return NULL;
                        fname = "-";
                        F = stdin;
-               } else {
-                       ind = getvar_s(incvar(intvar[ARGIND]));
-                       fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
-                       if (fname && *fname && !is_assignment(fname))
-                               F = xfopen_stdin(fname);
+                       break;
+               }
+               ind = getvar_s(incvar(intvar[ARGIND]));
+               fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
+               if (fname && *fname && !is_assignment(fname)) {
+                       F = xfopen_stdin(fname);
+                       break;
                }
-       } while (!F);
+       }
 
        files_happen = TRUE;
        setvar_s(intvar[FILENAME], fname);
@@ -2755,12 +3152,15 @@ static rstream *next_input_file(void)
 }
 
 int awk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int awk_main(int argc, char **argv)
+int awk_main(int argc UNUSED_PARAM, char **argv)
 {
        unsigned opt;
-       char *opt_F, *opt_W;
+       char *opt_F;
        llist_t *list_v = NULL;
        llist_t *list_f = NULL;
+#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
+       llist_t *list_e = NULL;
+#endif
        int i, j;
        var *v;
        var tv;
@@ -2819,56 +3219,61 @@ int awk_main(int argc, char **argv)
                        *s1 = '=';
                }
        }
-       opt_complementary = "v::f::"; /* -v and -f can occur multiple times */
-       opt = getopt32(argv, "F:v:f:W:", &opt_F, &list_v, &list_f, &opt_W);
+       opt = getopt32(argv, OPTSTR_AWK, &opt_F, &list_v, &list_f, IF_FEATURE_AWK_GNU_EXTENSIONS(&list_e,) NULL);
        argv += optind;
-       argc -= optind;
-       if (opt & 0x1)
-               setvar_s(intvar[FS], opt_F); // -F
-       while (list_v) { /* -v */
+       //argc -= optind;
+       if (opt & OPT_W)
+               bb_error_msg("warning: option -W is ignored");
+       if (opt & OPT_F) {
+               unescape_string_in_place(opt_F);
+               setvar_s(intvar[FS], opt_F);
+       }
+       while (list_v) {
                if (!is_assignment(llist_pop(&list_v)))
                        bb_show_usage();
        }
-       if (list_f) { /* -f */
-               do {
-                       char *s = NULL;
-                       FILE *from_file;
-
-                       g_progname = llist_pop(&list_f);
-                       from_file = xfopen_stdin(g_progname);
-                       /* one byte is reserved for some trick in next_token */
-                       for (i = j = 1; j > 0; i += j) {
-                               s = xrealloc(s, i + 4096);
-                               j = fread(s + i, 1, 4094, from_file);
-                       }
-                       s[i] = '\0';
-                       fclose(from_file);
-                       parse_program(s + 1);
-                       free(s);
-               } while (list_f);
-       } else { // no -f: take program from 1st parameter
-               if (!argc)
+       while (list_f) {
+               char *s = NULL;
+               FILE *from_file;
+
+               g_progname = llist_pop(&list_f);
+               from_file = xfopen_stdin(g_progname);
+               /* one byte is reserved for some trick in next_token */
+               for (i = j = 1; j > 0; i += j) {
+                       s = xrealloc(s, i + 4096);
+                       j = fread(s + i, 1, 4094, from_file);
+               }
+               s[i] = '\0';
+               fclose(from_file);
+               parse_program(s + 1);
+               free(s);
+       }
+       g_progname = "cmd. line";
+#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
+       while (list_e) {
+               parse_program(llist_pop(&list_e));
+       }
+#endif
+       if (!(opt & (OPT_f | OPT_e))) {
+               if (!*argv)
                        bb_show_usage();
-               g_progname = "cmd. line";
                parse_program(*argv++);
-               argc--;
        }
-       if (opt & 0x8) // -W
-               bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
 
        /* fill in ARGV array */
-       setvar_i(intvar[ARGC], argc + 1);
        setari_u(intvar[ARGV], 0, "awk");
        i = 0;
        while (*argv)
                setari_u(intvar[ARGV], ++i, *argv++);
+       setvar_i(intvar[ARGC], i + 1);
 
        evaluate(beginseq.first, &tv);
        if (!mainseq.first && !endseq.first)
                awk_exit(EXIT_SUCCESS);
 
        /* input file could already be opened in BEGIN block */
-       if (!iF) iF = next_input_file();
+       if (!iF)
+               iF = next_input_file();
 
        /* passing through input files */
        while (iF) {