awk: do not allow $(-1)
[oweals/busybox.git] / editors / awk.c
index 0918026d7339add22c6c192a818e1a4322bc9421..bafc9ba1d382b6bbb2651469aefd12b1161625b5 100644 (file)
@@ -6,6 +6,34 @@
  *
  * 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]..."
@@ -13,6 +41,9 @@
 //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"
@@ -25,6 +56,7 @@
  * 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__))
 #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
@@ -151,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)                /* | > >> */
@@ -171,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 \
@@ -238,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
@@ -287,54 +353,54 @@ enum {
 #define NTC     "\377"  /* switch to next token class (tc<<1) */
 #define NTCC    '\377'
 
-#define OC_B  OC_BUILTIN
-
 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 */
+       "\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
-       "\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 */
+       "\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
-       "\4else"      NTC
-
-       "\3and"       "\5compl"     "\6lshift"  "\2or"
+       "\5while"     NTC                                   /* TC_WHILE */
+       "\4else"      NTC                                   /* TC_ELSE */
+       "\3and"       "\5compl"     "\6lshift"  "\2or"      /* TC_BUILTIN */
        "\6rshift"    "\3xor"
-       "\5close"     "\6system"    "\6fflush"  "\5atan2"   /* BUILTIN */
+       "\5close"     "\6system"    "\6fflush"  "\5atan2"
        "\3cos"       "\3exp"       "\3int"     "\3log"
        "\4rand"      "\3sin"       "\4sqrt"    "\5srand"
-       "\6gensub"    "\4gsub"      "\5index"   "\6length"
+       "\6gensub"    "\4gsub"      "\5index"   /* "\6length" was here */
        "\5match"     "\5split"     "\7sprintf" "\3sub"
        "\6substr"    "\7systime"   "\10strftime" "\6mktime"
        "\7tolower"   "\7toupper"   NTC
-       "\7getline"   NTC
-       "\4func"      "\10function" NTC
-       "\5BEGIN"     NTC
-       "\3END"
+       "\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,
@@ -349,7 +415,7 @@ static const uint32_t tokeninfo[] = {
        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), /* in */
+       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)|'!',
@@ -364,20 +430,20 @@ static const uint32_t tokeninfo[] = {
        OC_RETURN|Vx, OC_EXIT|Nx,
        ST_WHILE,
        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_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 /* END */
+       0 /* TC_END */
 };
 
 /* internal variable names and their initial values       */
@@ -432,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 */
 
@@ -496,11 +562,11 @@ 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_lineno     (G.t_lineno    )
-#define t_rollback   (G.t_rollback  )
 #define intvar       (G.intvar      )
 #define fsplitter    (G.fsplitter   )
 #define rsplitter    (G.rsplitter   )
@@ -532,6 +598,7 @@ 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";
 static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
+static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field";
 
 static void zero_out_var(var *vp)
 {
@@ -689,6 +756,10 @@ static char nextchar(char **s)
        pps = *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)
@@ -697,6 +768,15 @@ static char nextchar(char **s)
        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 == '_');
@@ -988,12 +1068,10 @@ static uint32_t next_token(uint32_t expected)
 
        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:
@@ -1008,7 +1086,7 @@ 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;
@@ -1023,7 +1101,7 @@ static uint32_t next_token(uint32_t expected)
                        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;
@@ -1045,6 +1123,7 @@ static uint32_t next_token(uint32_t expected)
                        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 */
@@ -1054,7 +1133,7 @@ static uint32_t next_token(uint32_t expected)
                        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;
@@ -1076,6 +1155,7 @@ static uint32_t next_token(uint32_t expected)
                                ) {
                                        /* 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;
                                        goto token_found;
                                }
@@ -1099,14 +1179,17 @@ static uint32_t next_token(uint32_t expected)
                                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: ;
                }
+ token_found:
                g_pos = p;
 
                /* skipping newlines in some cases */
@@ -1127,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
@@ -1178,6 +1262,8 @@ 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;
@@ -1186,12 +1272,14 @@ static node *parse_expr(uint32_t 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;
@@ -1221,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;
@@ -1228,12 +1317,14 @@ 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) {
@@ -1250,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)
@@ -1259,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;
 }
 
@@ -1353,18 +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) {
+                       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();
@@ -1379,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;
@@ -1393,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;
@@ -1418,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) {
@@ -1429,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);
                }
        }
@@ -1457,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);
@@ -1485,24 +1615,27 @@ static void parse_program(char *p)
                        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__);
 }
 
 
@@ -1743,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);
@@ -1934,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)) {
@@ -2373,6 +2515,32 @@ static var *evaluate(node *op, var *res)
                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 */
                if (opinfo & OF_RES1)
                        L.v = evaluate(op1, v1);
@@ -2480,28 +2648,7 @@ static var *evaluate(node *op, var *res)
                        break;
                }
 
-               case XC( OC_DELETE ): {
-                       uint32_t info = op1->info & OPCLSMASK;
-                       var *v;
-
-                       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) {
-                               const char *s;
-                               clrvar(L.v);
-                               s = getvar_s(evaluate(op1->r.n, v1));
-                               hash_remove(iamarray(v), s);
-                       } else {
-                               clear_array(iamarray(v));
-                       }
-                       break;
-               }
+               /* case XC( OC_DELETE ): - moved to happen before arg evaluation */
 
                case XC( OC_NEWSOURCE ):
                        g_progname = op->l.new_progname;
@@ -2525,12 +2672,14 @@ static var *evaluate(node *op, var *res)
                /* -- 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 ):
+                       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;
@@ -2580,7 +2729,8 @@ static var *evaluate(node *op, var *res)
                        var *vbeg, *v;
                        const char *sv_progname;
 
-                       if (!op->r.f->body.first)
+                       /* 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);
 
                        vbeg = v = nvalloc(op->r.f->nargs + 1);
@@ -2651,7 +2801,7 @@ static var *evaluate(node *op, var *res)
 
                        switch (opn) {
                        case F_in:
-                               R_d = (int)L_d;
+                               R_d = (long long)L_d;
                                break;
 
                        case F_rn:
@@ -2702,8 +2852,16 @@ static var *evaluate(node *op, var *res)
                                break;
 
                        case F_le:
-                               if (!op1)
+                               debug_printf_eval("length: L.s:'%s'\n", L.s);
+                               if (!op1) {
                                        L.s = getvar_s(intvar[F0]);
+                                       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;
 
@@ -2792,6 +2950,8 @@ static var *evaluate(node *op, var *res)
 
                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 {
@@ -2849,7 +3009,7 @@ static var *evaluate(node *op, var *res)
                        case '%':
                                if (R_d == 0)
                                        syntax_error(EMSG_DIV_BY_ZERO);
-                               L_d -= (int)(L_d / R_d) * R_d;
+                               L_d -= (long long)(L_d / R_d) * R_d;
                                break;
                        }
                        debug_printf_eval("BINARY/REPLACE result:%f\n", L_d);
@@ -2885,7 +3045,8 @@ static var *evaluate(node *op, var *res)
 
                default:
                        syntax_error(EMSG_POSSIBLE_ERROR);
-               }
+               } /* switch */
+ next:
                if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
                        op = op->a.n;
                if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
@@ -2936,7 +3097,7 @@ static int awk_exit(int r)
  * otherwise return 0 */
 static int is_assignment(const char *expr)
 {
-       char *exprc, *val, *s, *s1;
+       char *exprc, *val;
 
        if (!isalnum_(*expr) || (val = strchr(expr, '=')) == NULL) {
                return FALSE;
@@ -2946,10 +3107,7 @@ static int is_assignment(const char *expr)
        val = exprc + (val - expr);
        *val++ = '\0';
 
-       s = s1 = val;
-       while ((*s1 = nextchar(&s)) != '\0')
-               s1++;
-
+       unescape_string_in_place(val);
        setvar_u(newvar(exprc), val);
        free(exprc);
        return TRUE;
@@ -2994,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;
@@ -3058,49 +3219,53 @@ 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);
-               argc++;
-       } 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++);
        }
-       if (opt & 0x8) // -W
-               bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
 
        /* fill in ARGV array */
-       setvar_i(intvar[ARGC], argc);
        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)