find:: get rid of nested function (it's a gcc-ism)
[oweals/busybox.git] / editors / awk.c
index 0918026d7339add22c6c192a818e1a4322bc9421..0b573a065c18bfd237886ee563f661c5199acc78 100644 (file)
@@ -25,6 +25,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__))
@@ -32,6 +33,9 @@
 #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
 
 
 
@@ -151,7 +155,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                       /* ( */
 #define        TC_SEQTERM      (1 << 1)                /* ) */
 #define        TC_REGEXP       (1 << 2)                /* /.../ */
 #define        TC_OUTRDR       (1 << 3)                /* | > >> */
@@ -238,6 +242,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
@@ -432,13 +439,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 +503,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   )
@@ -689,6 +696,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 +708,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 == '_');
@@ -1008,6 +1028,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 */
@@ -1023,6 +1044,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 */
@@ -1045,6 +1067,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,6 +1077,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 */
@@ -1076,6 +1100,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 +1124,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 */
@@ -1178,6 +1206,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 +1216,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 +1253,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 +1261,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 +1285,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 +1295,41 @@ 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;
                                }
                        }
                }
        }
+
+       debug_printf_parse("%s() returns %p\n", __func__, sn.r.n);
        return sn.r.n;
 }
 
@@ -1353,18 +1398,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 +1431,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,6 +1447,7 @@ 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 */
@@ -1418,6 +1473,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 +1485,20 @@ 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;
                        break;
 
                case OC_CONTINUE:
+                       debug_printf_parse("%s: OC_CONTINUE\n", __func__);
                        n = chain_node(OC_EXEC);
                        n->a.n = continue_ptr;
                        break;
 
                /* delete, next, nextfile, return, exit */
                default:
+                       debug_printf_parse("%s: default\n", __func__);
                        chain_expr(t_info);
                }
        }
@@ -1457,19 +1516,24 @@ 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);
@@ -1487,22 +1551,27 @@ static void parse_program(char *p)
                        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,6 +1812,18 @@ static void handle_special(var *v)
                is_f0_split = FALSE;
 
        } else if (v == intvar[FS]) {
+               /*
+                * 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]) {
@@ -2580,7 +2661,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)
                                syntax_error(EMSG_UNDEF_FUNC);
 
                        vbeg = v = nvalloc(op->r.f->nargs + 1);
@@ -2936,7 +3018,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 +3028,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;
@@ -2997,7 +3076,7 @@ int awk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int awk_main(int argc, char **argv)
 {
        unsigned opt;
-       char *opt_F, *opt_W;
+       char *opt_F;
        llist_t *list_v = NULL;
        llist_t *list_f = NULL;
        int i, j;
@@ -3059,11 +3138,13 @@ int awk_main(int argc, char **argv)
                }
        }
        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, "F:v:f:W:", &opt_F, &list_v, &list_f, NULL);
        argv += optind;
        argc -= optind;
-       if (opt & 0x1)
-               setvar_s(intvar[FS], opt_F); // -F
+       if (opt & 0x1) { /* -F */
+               unescape_string_in_place(opt_F);
+               setvar_s(intvar[FS], opt_F);
+       }
        while (list_v) { /* -v */
                if (!is_assignment(llist_pop(&list_v)))
                        bb_show_usage();
@@ -3093,7 +3174,7 @@ int awk_main(int argc, char **argv)
                parse_program(*argv++);
        }
        if (opt & 0x8) // -W
-               bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
+               bb_error_msg("warning: option -W is ignored");
 
        /* fill in ARGV array */
        setvar_i(intvar[ARGC], argc);