Github messages for mblaze
 help / color / mirror / Atom feed
* Re: [PR PATCH] [Updated] mpick craziness
       [not found] <gh-mailinglist-notifications-fa6558a0-26e0-48f6-803f-f5a8af34f6a8-mblaze-150@inbox.vuxu.org>
@ 2020-05-15 16:37 ` Duncaen
  0 siblings, 0 replies; only message in thread
From: Duncaen @ 2020-05-15 16:37 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 289 bytes --]

There is an updated pull request by Duncaen against master on the mblaze repository

https://github.com/Duncaen/mblaze mpick-craziness
https://github.com/leahneukirchen/mblaze/pull/150

mpick craziness


A patch file from https://github.com/leahneukirchen/mblaze/pull/150.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-mpick-craziness-150.patch --]
[-- Type: text/x-diff, Size: 54954 bytes --]

From d62ee33aeee47f7388f2c8636c7414ef414306fa Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Sun, 27 Jan 2019 22:18:22 +0100
Subject: [PATCH 01/20] mdeliver.1: add mrefile to SEE ALSO

---
 man/mdeliver.1 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/man/mdeliver.1 b/man/mdeliver.1
index 07bc476..36a890f 100644
--- a/man/mdeliver.1
+++ b/man/mdeliver.1
@@ -69,6 +69,7 @@ Override the flags of the new message file to be
 .Ex -std
 .Sh SEE ALSO
 .Xr mexport 1 ,
+.Xr mrefile 1 ,
 .Xr maildir 5 ,
 .Xr mbox 5
 .Pp

From 694b7965c04737327052265d8e6eff16b9dc8849 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 10:53:05 +0100
Subject: [PATCH 02/20] mpick: add pipes and file redirection

---
 man/mpick.1 |   3 ++
 mpick.c     | 114 +++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 112 insertions(+), 5 deletions(-)

diff --git a/man/mpick.1 b/man/mpick.1
index 89bf619..2ad942e 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -96,6 +96,9 @@ tests are given by the following EBNF:
              | <expr> && <expr>  -- conjunction
              | ! <expr>          -- negation
              | ( <expr> )
+             | <expr> "|"  <str> -- pipe current mail to command
+             | <expr> ">>" <str> -- append current mail to file
+             | <expr> ">"  <str> -- write current mail to file
              | <flagprop>
              | <timeprop> <numop> <dur>
              | <numprop> <numop> <num>
diff --git a/mpick.c b/mpick.c
index 1704044..0427519 100644
--- a/mpick.c
+++ b/mpick.c
@@ -61,6 +61,8 @@ enum op {
 	EXPR_REGEXI,
 	EXPR_PRUNE,
 	EXPR_PRINT,
+	EXPR_REDIR_FILE,
+	EXPR_REDIR_PIPE,
 	EXPR_TYPE,
 	EXPR_ALLSET,
 	EXPR_ANYSET,
@@ -143,6 +145,14 @@ struct thread {
 	struct mlist *cur;
 };
 
+struct file {
+	enum op op;
+	const char *name;
+	const char *mode;
+	FILE *fp;
+	struct file *next;
+};
+
 static struct thread *thr;
 
 static char *argv0;
@@ -160,6 +170,8 @@ static char *pos;
 static time_t now;
 static int prune;
 
+static struct file *files, *fileq = NULL;
+
 static void
 ws()
 {
@@ -179,6 +191,12 @@ token(char *token)
 	}
 }
 
+static int
+peek(char *token)
+{
+	return strncmp(pos, token, strlen(token)) == 0;
+}
+
 noreturn static void
 parse_error(char *msg, ...)
 {
@@ -642,14 +660,43 @@ parse_timecmp()
 	return 0;
 }
 
+static struct expr *
+parse_redir(struct expr *e)
+{
+	char *s;
+	const char *m;
+
+	if (peek("||"))
+		return e;
+
+	if (token("|")) {
+		if (!parse_string(&s))
+			parse_error("expected command");
+		struct expr *r = mkexpr(EXPR_REDIR_PIPE);
+		r->a.string = s;
+		r->b.string = "w";
+		return chain(e, EXPR_AND, r);
+	}
+	else if (token(">>")) m = "a+";
+	else if (token(">")) m = "w+";
+	else return e;
+
+	if (!parse_string(&s))
+		parse_error("expected file name");
+	struct expr *r = mkexpr(EXPR_REDIR_FILE);
+	r->a.string = s;
+	r->b.string = m;
+	return chain(e, EXPR_AND, r);
+}
+
 static struct expr *
 parse_and()
 {
-	struct expr *e1 = parse_timecmp();
+	struct expr *e1 = parse_redir(parse_timecmp());
 	struct expr *r = e1;
 
 	while (token("&&")) {
-		struct expr *e2 = parse_timecmp();
+		struct expr *e2 = parse_redir(parse_timecmp());
 		r = chain(r, EXPR_AND, e2);
 	}
 
@@ -843,9 +890,48 @@ msg_addr(struct mailinfo *m, char *h, int t)
 	}
 }
 
+FILE *
+redir(struct expr *e)
+{
+	struct file *file;
+	FILE *fp;
+
+	for (file = files; file; file = file->next) {
+		if (e->op == file->op &&
+		    strcmp(e->a.string, file->name) == 0 &&
+			strcmp(e->b.string, file->mode) == 0)
+			return file->fp;
+	}
+
+	fflush(stdout);
+	fp = NULL;
+	switch (e->op) {
+	case EXPR_REDIR_FILE: fp = fopen(e->a.string, e->b.string); break;
+	case EXPR_REDIR_PIPE: fp = popen(e->a.string, e->b.string); break;
+	}
+	if (!fp) {
+		fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno));
+		exit(3);
+	}
+	file = calloc(1, sizeof (struct file));
+	if (!file) {
+		perror("calloc");
+		exit(2);
+	}
+	file->op = e->op;
+	file->name = e->a.string;
+	file->mode = e->b.string;
+	file->fp = fp;
+	if (!files) files = file;
+	if (fileq) fileq->next = file;
+	fileq = file;
+	return fp;
+}
+
 int
 eval(struct expr *e, struct mailinfo *m)
 {
+	FILE *fp;
 	switch (e->op) {
 	case EXPR_OR:
 		return eval(e->a.expr, m) || eval(e->b.expr, m);
@@ -859,6 +945,13 @@ eval(struct expr *e, struct mailinfo *m)
 		return 1;
 	case EXPR_PRINT:
 		return 1;
+	case EXPR_REDIR_FILE:
+	case EXPR_REDIR_PIPE:
+		fp = redir(e);
+		fputs(m->fpath, fp);
+		putc('\n', fp);
+		fflush(fp);
+		return 1;
 	case EXPR_LT:
 	case EXPR_LE:
 	case EXPR_EQ:
@@ -1040,10 +1133,10 @@ do_thr()
 		if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) {
 			int i;
 			for (i = 0; i < ml->m->depth; i++)
-				putchar(' ');
+				putc(' ', stdout);
 
 			fputs(ml->m->fpath, stdout);
-			putchar('\n');
+			putc('\n', stdout);
 
 			kept++;
 		}
@@ -1126,7 +1219,8 @@ oneline(char *file)
 		goto out;
 
 	fputs(file, stdout);
-	putchar('\n');
+	putc('\n', stdout);
+	fflush(stdout);
 	kept++;
 
 out:
@@ -1174,5 +1268,15 @@ main(int argc, char *argv[])
 
 	if (vflag)
 		fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
+
+	for (; files; files = fileq) {
+		fileq = files->next;
+		if (files->op == EXPR_REDIR_PIPE)
+			pclose(files->fp);
+		else
+			fclose(files->fp);
+		free(files);
+	}
+
 	return 0;
 }

From c1fee74966c8ec70871364f205fd39604e696bc8 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 10:58:27 +0100
Subject: [PATCH 03/20] mpick: add skip expression

---
 man/mpick.1 | 1 +
 mpick.c     | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/man/mpick.1 b/man/mpick.1
index 2ad942e..64a63d1 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -105,6 +105,7 @@ tests are given by the following EBNF:
              | <strprop> <strop> <str>
              | prune             -- do not match further messages in thread
              | print             -- always true value
+             | skip              -- always false value
 
 <flagprop> ::= child | draft | flagged | info | new | parent | passed
              | replied  | seen | selected | trashed
diff --git a/mpick.c b/mpick.c
index 0427519..c4a4207 100644
--- a/mpick.c
+++ b/mpick.c
@@ -274,6 +274,11 @@ parse_inner()
 	} else if (token("print")) {
 		struct expr *e = mkexpr(EXPR_PRINT);
 		return e;
+	} else if (token("skip")) {
+		struct expr *e = mkexpr(EXPR_PRINT);
+		struct expr *not = mkexpr(EXPR_NOT);
+		not->a.expr = e;
+		return not;
 	} else if (token("!")) {
 		struct expr *e = parse_cmp();
 		struct expr *not = mkexpr(EXPR_NOT);

From 8e29dd22917b4dbdeff2da9f48e8f705651d278e Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 11:12:18 +0100
Subject: [PATCH 04/20] mpick: add ternary (conditional) operator

---
 man/mpick.1 |  3 ++-
 mpick.c     | 38 +++++++++++++++++++++++++++++++++-----
 2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/man/mpick.1 b/man/mpick.1
index 64a63d1..744fa24 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -92,7 +92,8 @@ Unread messages.
 .Nm
 tests are given by the following EBNF:
 .Bd -literal
-<expr>     ::= <expr> || <expr>  -- disjunction
+<expr>     ::= <expr> ? <expr> : <expr>  -- ternary operator
+             | <expr> || <expr>  -- disjunction
              | <expr> && <expr>  -- conjunction
              | ! <expr>          -- negation
              | ( <expr> )
diff --git a/mpick.c b/mpick.c
index c4a4207..f9da79d 100644
--- a/mpick.c
+++ b/mpick.c
@@ -46,6 +46,7 @@
 enum op {
 	EXPR_OR = 1,
 	EXPR_AND,
+	EXPR_COND,
 	EXPR_NOT,
 	EXPR_LT,
 	EXPR_LE,
@@ -115,7 +116,7 @@ struct expr {
 		int64_t num;
 		regex_t *regex;
 		enum var var;
-	} a, b;
+	} a, b, c;
 	int extra;
 };
 
@@ -263,7 +264,7 @@ parse_op()
 }
 
 static struct expr *parse_cmp();
-static struct expr *parse_or();
+static struct expr *parse_cond();
 
 static struct expr *
 parse_inner()
@@ -285,7 +286,7 @@ parse_inner()
 		not->a.expr = e;
 		return not;
 	} else if (token("(")) {
-		struct expr *e = parse_or();
+		struct expr *e = parse_cond();
 		if (token(")"))
 			return e;
 		parse_error("missing ) at '%.15s'", pos);
@@ -723,10 +724,33 @@ parse_or()
 }
 
 static struct expr *
-parse_expr(char *s)
+parse_cond()
+{
+	struct expr *e1 = parse_or();
+
+	if (token("?")) {
+		struct expr *e2 = parse_or();
+		if (token(":")) {
+			struct expr *e3 = parse_cond();
+			struct expr *r = mkexpr(EXPR_COND);
+			r->a.expr = e1;
+			r->b.expr = e2;
+			r->c.expr = e3;
+
+			return r;
+		} else {
+			parse_error("expected : at '%.15s'", pos);
+		}
+	}
+
+	return e1;
+}
+
+static struct expr *
+parse_expr()
 {
 	pos = s;
-	struct expr *e = parse_or();
+	struct expr *e = parse_cond();
 	if (*pos)
 		parse_error("trailing garbage at '%.15s'", pos);
 	return e;
@@ -942,6 +966,10 @@ eval(struct expr *e, struct mailinfo *m)
 		return eval(e->a.expr, m) || eval(e->b.expr, m);
 	case EXPR_AND:
 		return eval(e->a.expr, m) && eval(e->b.expr, m);
+	case EXPR_COND:
+		return eval(e->a.expr, m)
+		    ? eval(e->b.expr, m)
+			: eval(e->c.expr, m);
 	case EXPR_NOT:
 		return !eval(e->a.expr, m);
 		return 1;

From 86f24ed094a81f9763b0ea048d365174a79a0d90 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 14:38:05 +0100
Subject: [PATCH 05/20] mpick: add freeexpr

---
 mpick.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/mpick.c b/mpick.c
index f9da79d..e1f9478 100644
--- a/mpick.c
+++ b/mpick.c
@@ -220,6 +220,50 @@ mkexpr(enum op op)
 	return e;
 }
 
+static void
+freeexpr(struct expr *e)
+{
+	if (!e) return;
+	switch (e->op) {
+	case EXPR_OR:
+	case EXPR_AND:
+		freeexpr(e->a.expr);
+		freeexpr(e->b.expr);
+		break;
+	case EXPR_COND:
+		freeexpr(e->a.expr);
+		freeexpr(e->b.expr);
+		freeexpr(e->c.expr);
+		break;
+	case EXPR_NOT:
+		freeexpr(e->a.expr);
+		break;
+	case EXPR_REDIR_FILE:
+	case EXPR_REDIR_PIPE:
+		free(e->a.string);
+		free(e->b.string);
+		break;
+	case EXPR_STREQ:
+	case EXPR_STREQI:
+	case EXPR_GLOB:
+	case EXPR_GLOBI:
+	case EXPR_REGEX:
+	case EXPR_REGEXI:
+		switch (e->a.prop) {
+		case PROP_PATH:
+		case PROP_FROM:
+		case PROP_TO:
+			break;
+		default: free(e->a.string);
+		}
+		if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
+			regfree(e->b.regex);
+		else
+			free(e->b.string);
+	}
+	free(e);
+}
+
 static struct expr *
 chain(struct expr *e1, enum op op, struct expr *e2)
 {
@@ -1299,6 +1343,8 @@ main(int argc, char *argv[])
 	if (Tflag && thr)
 		do_thr();
 
+	freeexpr(expr);
+
 	if (vflag)
 		fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
 

From ce5bab3f5fb0c17b11de579ecf7c90541f265b0e Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 14:45:40 +0100
Subject: [PATCH 06/20] mpick: add xcalloc and xstrdup

---
 mpick.c | 66 +++++++++++++++++++++++++++++++++------------------------
 1 file changed, 38 insertions(+), 28 deletions(-)

diff --git a/mpick.c b/mpick.c
index e1f9478..78509d4 100644
--- a/mpick.c
+++ b/mpick.c
@@ -173,6 +173,26 @@ static int prune;
 
 static struct file *files, *fileq = NULL;
 
+static void *
+xcalloc(size_t nmemb, size_t size)
+{
+	void *r;
+	if ((r = calloc(nmemb, size)))
+		return r;
+	perror("calloc");
+	exit(2);
+}
+
+static char *
+xstrdup(const char *s)
+{
+	char *r;
+	if ((r = strdup(s)))
+		return r;
+	perror("strdup");
+	exit(2);
+}
+
 static void
 ws()
 {
@@ -460,28 +480,27 @@ parse_strcmp()
 		char *disp, *addr;
 		blaze822_addr(s, &disp, &addr);
 		if (!disp && !addr)
-			parse_error("invalid address at '%.15s'", pos);
-		s = strdup((disp) ? disp : addr);
+			parse_error_at(NULL, "invalid address");
+		free(s);
+		s = xstrdup((disp) ? disp : addr);
 		e->extra = (disp) ? 0 : 1;
 	}
 
-	if (op == EXPR_REGEX) {
-		e->b.regex = malloc(sizeof (regex_t));
-		r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB);
-	} else if (op == EXPR_REGEXI) {
+	if (op == EXPR_REGEX || op == EXPR_REGEXI) {
 		e->b.regex = malloc(sizeof (regex_t));
-		r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+		r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB |
+		    (op == EXPR_REGEXI ? REG_ICASE : 0));
+		if (r != 0) {
+			char msg[256];
+			regerror(r, e->b.regex, msg, sizeof msg);
+			parse_error("invalid regex '%s': %s", s, msg);
+			exit(2);
+		}
+		free(s);
 	} else {
 		e->b.string = s;
 	}
 
-	if (r != 0) {
-		char msg[256];
-		regerror(r, e->b.regex, msg, sizeof msg);
-		parse_error("invalid regex '%s': %s", s, msg);
-		exit(2);
-	}
-
 	if (negate) {
 		struct expr *not = mkexpr(EXPR_NOT);
 		not->a.expr = e;
@@ -724,7 +743,7 @@ parse_redir(struct expr *e)
 			parse_error("expected command");
 		struct expr *r = mkexpr(EXPR_REDIR_PIPE);
 		r->a.string = s;
-		r->b.string = "w";
+		r->b.string = xstrdup("w");
 		return chain(e, EXPR_AND, r);
 	}
 	else if (token(">>")) m = "a+";
@@ -735,7 +754,7 @@ parse_redir(struct expr *e)
 		parse_error("expected file name");
 	struct expr *r = mkexpr(EXPR_REDIR_FILE);
 	r->a.string = s;
-	r->b.string = m;
+	r->b.string = xstrdup(m);
 	return chain(e, EXPR_AND, r);
 }
 
@@ -883,7 +902,7 @@ parse_msglist(char *s)
 			if (!disp && !addr)
 				parse_error("invalid address at '%.15s'", pos);
 
-			d = strdup((disp) ? disp : addr);
+			d = xstrdup((disp) ? disp : addr);
 
 			e1 = mkexpr(EXPR_REGEXI);
 			e1->a.prop = PROP_FROM;
@@ -986,11 +1005,7 @@ redir(struct expr *e)
 		fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno));
 		exit(3);
 	}
-	file = calloc(1, sizeof (struct file));
-	if (!file) {
-		perror("calloc");
-		exit(2);
-	}
+	file = xcalloc(1, sizeof (struct file));
 	file->op = e->op;
 	file->name = e->a.string;
 	file->mode = e->b.string;
@@ -1247,12 +1262,7 @@ collect(char *file)
 			do_thr();
 
 		/* new thread */
-		thr = calloc(1, sizeof *thr);
-		if (!thr) {
-			fprintf(stderr, "calloc");
-			exit(2);
-		}
-
+		thr = xcalloc(1, sizeof *thr);
 		thr->matched = 0;
 		ml = thr->cur = thr->childs;
 		thr->cur->m = m;

From 067f81e363d245abb11d1670bed5cef0b0c6fcad Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:00:56 +0100
Subject: [PATCH 07/20] mpick: add support for multiline expressions and
 comments

---
 mpick.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 57 insertions(+), 12 deletions(-)

diff --git a/mpick.c b/mpick.c
index 78509d4..eb6afc9 100644
--- a/mpick.c
+++ b/mpick.c
@@ -154,6 +154,12 @@ struct file {
 	struct file *next;
 };
 
+struct pos {
+	char *pos;
+	char *line;
+	size_t linenr;
+};
+
 static struct thread *thr;
 
 static char *argv0;
@@ -167,10 +173,14 @@ static long num;
 static struct expr *expr;
 static long cur_idx;
 static char *cur;
-static char *pos;
 static time_t now;
 static int prune;
 
+static char *pos;
+static const char *fname;
+static char *line = NULL;
+static int linenr = 0;
+
 static struct file *files, *fileq = NULL;
 
 static void *
@@ -196,8 +206,21 @@ xstrdup(const char *s)
 static void
 ws()
 {
-	while (isspace((unsigned char)*pos))
-		pos++;
+	for (; *pos;) {
+		while (isspace((unsigned char)*pos)) {
+			if (*pos == '\n') {
+				line = pos+1;
+				linenr++;
+			}
+			pos++;
+		}
+		if (*pos != '#')
+			break;
+
+		pos += strcspn(pos, "\n\0");
+		if (*pos != '\n')
+			break;
+	}
 }
 
 static int
@@ -223,12 +246,34 @@ parse_error(char *msg, ...)
 {
 	va_list ap;
 	va_start(ap, msg);
-	fprintf(stderr, "%s: parse error: ", argv0);
+	fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
 	vfprintf(stderr, msg, ap);
 	fprintf(stderr, "\n");
 	exit(2);
 }
 
+noreturn static void
+parse_error_at(struct pos *savepos, char *msg, ...)
+{
+	char *e;
+
+	if (savepos) {
+		pos = savepos->pos;
+		line = savepos->line;
+		linenr = savepos->linenr;
+	}
+
+	va_list ap;
+	va_start(ap, msg);
+	fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
+	vfprintf(stderr, msg, ap);
+	fprintf(stderr, " at '");
+	for (e = pos+15; *pos && *pos != '\n' && pos <= e; pos++)
+		putc(*pos, stderr);
+	fprintf(stderr, "'\n");
+	exit(2);
+}
+
 static struct expr *
 mkexpr(enum op op)
 {
@@ -460,11 +505,11 @@ parse_strcmp()
 	else if (token("!==") || token("!="))
 		negate = 1, op = EXPR_STREQ;
 	else
-		parse_error("invalid string operator at '%.15s'", pos);
+		parse_error_at(NULL, "invalid string operator");
 
 	char *s;
 	if (!parse_string(&s)) {
-		parse_error("invalid string at '%.15s'", pos);
+		parse_error_at(NULL, "invalid string");
 		return 0;
 	}
 
@@ -598,7 +643,7 @@ parse_cmp()
 		return parse_flag();
 
 	if (!(op = parse_op()))
-		parse_error("invalid comparison at '%.15s'", pos);
+		parse_error_at(NULL, "invalid comparison");
 
 	int64_t n;
 	if (parse_num(&n)) {
@@ -716,7 +761,7 @@ parse_timecmp()
 
 	op = parse_op();
 	if (!op)
-		parse_error("invalid comparison at '%.15s'", pos);
+		parse_error_at(NULL, "invalid comparison");
 
 	int64_t n;
 	if (parse_num(&n) || parse_dur(&n)) {
@@ -740,7 +785,7 @@ parse_redir(struct expr *e)
 
 	if (token("|")) {
 		if (!parse_string(&s))
-			parse_error("expected command");
+			parse_error_at(NULL, "expected command");
 		struct expr *r = mkexpr(EXPR_REDIR_PIPE);
 		r->a.string = s;
 		r->b.string = xstrdup("w");
@@ -751,7 +796,7 @@ parse_redir(struct expr *e)
 	else return e;
 
 	if (!parse_string(&s))
-		parse_error("expected file name");
+		parse_error_at(NULL, "expected file name");
 	struct expr *r = mkexpr(EXPR_REDIR_FILE);
 	r->a.string = s;
 	r->b.string = xstrdup(m);
@@ -802,7 +847,7 @@ parse_cond()
 
 			return r;
 		} else {
-			parse_error("expected : at '%.15s'", pos);
+			parse_error_at(NULL, "expected :", pos);
 		}
 	}
 
@@ -815,7 +860,7 @@ parse_expr()
 	pos = s;
 	struct expr *e = parse_cond();
 	if (*pos)
-		parse_error("trailing garbage at '%.15s'", pos);
+		parse_error_at(NULL, "trailing garbage");
 	return e;
 }
 

From 35f473afc6138a7b5d0060471b61bac821a64012 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:10:24 +0100
Subject: [PATCH 08/20] mpick: refactor mailfile handling

- fix some memory leaks
- fix handling of unexisting files/mthread containers
- in oneline mode use one mailinfo struct for each line
---
 mpick.c | 131 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 66 insertions(+), 65 deletions(-)

diff --git a/mpick.c b/mpick.c
index eb6afc9..27ee831 100644
--- a/mpick.c
+++ b/mpick.c
@@ -121,6 +121,7 @@ struct expr {
 };
 
 struct mailinfo {
+	char *file;
 	char *fpath;
 	struct stat *sb;
 	struct message *msg;
@@ -973,8 +974,13 @@ msg_date(struct mailinfo *m)
 	if (m->date)
 		return m->date;
 
-	if (!m->msg)
-		m->msg = blaze822(m->fpath);
+	// XXX: date comparisation should handle zero dates
+	if (!m->msg && m->fpath) {
+		if (!(m->msg = blaze822(m->fpath))) {
+			m->fpath = NULL;
+			return -1;
+		}
+	}
 
 	char *b;
 	if (m->msg && (b = blaze822_hdr(m->msg, "date")))
@@ -986,28 +992,35 @@ msg_date(struct mailinfo *m)
 char *
 msg_hdr(struct mailinfo *m, const char *h)
 {
-	if (!m->msg)
-		m->msg = blaze822(m->fpath);
+	static char hdrbuf[4096];
 
-	char *b;
-	if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
-		goto err;
+	if (!m->msg && m->fpath) {
+		if (!(m->msg = blaze822(m->fpath))) {
+			m->fpath = NULL;
+			*hdrbuf = 0;
+			return hdrbuf;
+		}
+	}
 
-	char buf[4096];
-	blaze822_decode_rfc2047(buf, b, sizeof buf - 1, "UTF-8");
-	if (!*buf)
-		goto err;
+	char *b;
+	if (!m->msg || !(b = blaze822_chdr(m->msg, h))) {
+		*hdrbuf = 0;
+		return hdrbuf;
+	}
 
-	return strdup(buf);
-err:
-	return "";
+	blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8");
+	return hdrbuf;
 }
 
 char *
 msg_addr(struct mailinfo *m, char *h, int t)
 {
-	if (!m->msg)
-		m->msg = blaze822(m->fpath);
+	if (!m->msg && m->fpath) {
+		if (!(m->msg = blaze822(m->fpath))) {
+			m->fpath = NULL;
+			return "";
+		}
+	}
 
 	char *b;
 	if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0)
@@ -1085,7 +1098,7 @@ eval(struct expr *e, struct mailinfo *m)
 	case EXPR_REDIR_FILE:
 	case EXPR_REDIR_PIPE:
 		fp = redir(e);
-		fputs(m->fpath, fp);
+		fputs(m->file, fp);
 		putc('\n', fp);
 		fflush(fp);
 		return 1;
@@ -1099,15 +1112,15 @@ eval(struct expr *e, struct mailinfo *m)
 	case EXPR_ANYSET: {
 		long v = 0, n;
 
-		if (!m->sb && (
+		if (!m->sb && m->fpath && (
 		    e->a.prop == PROP_ATIME ||
 		    e->a.prop == PROP_CTIME ||
 		    e->a.prop == PROP_MTIME ||
-		    e->a.prop == PROP_SIZE) &&
-		    (m->sb = calloc(1, sizeof *m->sb)) &&
-		    stat(m->fpath, m->sb) != 0) {
-			fprintf(stderr, "stat");
-			exit(2);
+		    e->a.prop == PROP_SIZE)) {
+			m->sb = xcalloc(1, sizeof *m->sb);
+			if (stat(m->fpath, m->sb) == -1)
+				m->fpath = NULL;
+			// XXX: stat based expressions should handle 0
 		}
 
 		n = e->b.num;
@@ -1154,9 +1167,9 @@ eval(struct expr *e, struct mailinfo *m)
 	case EXPR_GLOBI:
 	case EXPR_REGEX:
 	case EXPR_REGEXI: {
-		const char *s = "";
+		const char *s;
 		switch (e->a.prop) {
-		case PROP_PATH: s = m->fpath; break;
+		case PROP_PATH: s = m->fpath ? m->fpath : ""; break;
 		case PROP_FROM: s = msg_addr(m, "from", e->extra); break;
 		case PROP_TO: s = msg_addr(m, "to", e->extra); break;
 		default: s = msg_hdr(m, e->a.string); break;
@@ -1175,7 +1188,7 @@ eval(struct expr *e, struct mailinfo *m)
 }
 
 struct mailinfo *
-mailfile(char *file)
+mailfile(struct mailinfo *m, char *file)
 {
 	static int init;
 	if (!init) {
@@ -1186,45 +1199,36 @@ mailfile(char *file)
 		cur = blaze822_seq_cur();
 		init = 1;
 	}
+	char *fpath = file;
 
-	struct mailinfo *m;
-	m = calloc(1, sizeof *m);
-	if (!m) {
-		fprintf(stderr, "calloc");
-		exit(2);
-	}
-	m->fpath = file;
 	m->index = num++;
-	m->flags = 0;
-	m->replies = 0;
-	m->depth = 0;
-	m->sb = 0;
-	m->msg = 0;
+	m->file = file;
 
-	while (*m->fpath == ' ' || *m->fpath == '\t') {
+	while (*fpath == ' ' || *fpath == '\t') {
 		m->depth++;
-		m->fpath++;
+		fpath++;
 	}
 
-	char *e = m->fpath + strlen(m->fpath) - 1;
-	while (m->fpath < e && (*e == ' ' || *e == '\t'))
+	char *e = fpath + strlen(fpath) - 1;
+	while (fpath < e && (*e == ' ' || *e == '\t'))
 		*e-- = 0;
 
-	if (m->fpath[0] == '<') {
+	if (fpath[0] == '<') {
 		m->flags |= FLAG_SEEN | FLAG_INFO;
+		m->fpath = NULL;
 		return m;
 	}
 
-	if ((e = strrchr(m->fpath, '/') - 1) && (e - m->fpath) >= 2 &&
+	if ((e = strrchr(fpath, '/') - 1) && (e - fpath) >= 2 &&
 	    *e-- == 'w' && *e-- == 'e' && *e-- == 'n')
 		m->flags |= FLAG_NEW;
 
-	if (cur && strcmp(cur, m->fpath) == 0) {
+	if (cur && strcmp(cur, fpath) == 0) {
 		m->flags |= FLAG_CUR;
 		cur_idx = m->index;
 	}
 
-	char *f = strstr(m->fpath, ":2,");
+	char *f = strstr(fpath, ":2,");
 	if (f) {
 		if (strchr(f, 'P'))
 			m->flags |= FLAG_PASSED;
@@ -1240,6 +1244,7 @@ mailfile(char *file)
 			m->flags |= FLAG_FLAGGED;
 	}
 
+	m->fpath = fpath;
 	return m;
 }
 
@@ -1268,11 +1273,7 @@ do_thr()
 			break;
 
 		if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) {
-			int i;
-			for (i = 0; i < ml->m->depth; i++)
-				putc(' ', stdout);
-
-			fputs(ml->m->fpath, stdout);
+			fputs(ml->m->file, stdout);
 			putc('\n', stdout);
 
 			kept++;
@@ -1285,7 +1286,7 @@ do_thr()
 		if (ml->m->sb)
 			free(ml->m->sb);
 
-		free(ml->m->fpath);
+		free(ml->m->file);
 		free(ml->m);
 	}
 
@@ -1298,11 +1299,14 @@ collect(char *file)
 {
 	struct mailinfo *m;
 	struct mlist *ml;
+	char *f;
 
-	if ((m = mailfile(file)) == 0)
-		return;
+	f = xstrdup(file);
+	m = xcalloc(1, sizeof *m);
+	m = mailfile(m, f);
 
 	if (m->depth == 0) {
+		/* process previous thread */
 		if (thr)
 			do_thr();
 
@@ -1337,17 +1341,15 @@ collect(char *file)
 
 	for (ml = ml->parent; ml; ml = ml->parent)
 		ml->m->replies++;
-
-	m->fpath = strdup(m->fpath);
 }
 
 void
 oneline(char *file)
 {
-	struct mailinfo *m;
-
-	m = mailfile(file);
-	if (expr && !eval(expr, m))
+	struct mailinfo m = { 0 };
+	m.index = num++;
+	(void) mailfile(&m, file);
+	if (expr && !eval(expr, &m))
 		goto out;
 
 	fputs(file, stdout);
@@ -1356,11 +1358,10 @@ oneline(char *file)
 	kept++;
 
 out:
-	if (m->msg)
-		blaze822_free(m->msg);
-	if (m->sb)
-		free(m->sb);
-	free(m);
+	if (m.msg)
+		blaze822_free(m.msg);
+	if (m.sb)
+		free(m.sb);
 }
 
 int

From 66958e44f0d5eafadc7bd909079597bc85a40762 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:23:27 +0100
Subject: [PATCH 09/20] mpick: minor cleanups/memleak fixes for parse_msglist

---
 mpick.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/mpick.c b/mpick.c
index 27ee831..16028a7 100644
--- a/mpick.c
+++ b/mpick.c
@@ -872,12 +872,13 @@ parse_msglist(char *s)
 	int r;
 	struct expr *e1, *e2;
 	char *d;
+	enum flags flag;
 
 	switch (*s) {
 	case '/':
 		s++;
 		e1 = mkexpr(EXPR_REGEXI);
-		e1->a.string = "subject";
+		e1->a.string = xstrdup("subject");
 		e1->b.regex = malloc(sizeof (regex_t));
 		r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
 		if (r != 0) {
@@ -887,13 +888,10 @@ parse_msglist(char *s)
 		}
 		return e1;
 	case ':':
-		if (strlen(s) <= 1)
-			parse_error("missing type at '%.15s'", s);
-
-		enum flags flag;
 		n = 0;
 
 		switch (*++s) {
+		case '\0': parse_error("missing flag at '%s'", s-1);
 		case 'P': flag = FLAG_PASSED; break;
 		case 'F': flag = FLAG_FLAGGED; break;
 		case 'D': flag = FLAG_DRAFT; break;
@@ -905,7 +903,7 @@ parse_msglist(char *s)
 		case 'o': n = 1; /* FALL THROUGH */
 		case 'n': flag = FLAG_NEW; break;
 		case 'R': flag = FLAG_REPLIED; break;
-		default: parse_error("unknown type at '%.15s'", s);
+		default: parse_error("unknown flag at '%s'", s);
 		}
 
 		e1 = mkexpr(EXPR_ANYSET);
@@ -946,9 +944,9 @@ parse_msglist(char *s)
 
 			blaze822_addr(s, &disp, &addr);
 			if (!disp && !addr)
-				parse_error("invalid address at '%.15s'", pos);
+				parse_error("invalid address '%s'", s);
 
-			d = xstrdup((disp) ? disp : addr);
+			d = (disp) ? disp : addr;
 
 			e1 = mkexpr(EXPR_REGEXI);
 			e1->a.prop = PROP_FROM;

From 0ec4e3332e53e44c56ccccda539675afbb51fb67 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:30:19 +0100
Subject: [PATCH 10/20] mpick: allow reading expressions from files i.e. using
 #!/bin/mpick

---
 mpick.c | 45 ++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 40 insertions(+), 5 deletions(-)

diff --git a/mpick.c b/mpick.c
index 16028a7..1a6c810 100644
--- a/mpick.c
+++ b/mpick.c
@@ -23,6 +23,7 @@
 #endif
 
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -858,8 +859,18 @@ parse_cond()
 static struct expr *
 parse_expr()
 {
-	pos = s;
-	struct expr *e = parse_cond();
+	ws();
+	return parse_cond();
+}
+
+static struct expr *
+parse_buf(const char *f, char *s)
+{
+	struct expr *e;
+	fname = f;
+	line = pos = s;
+	linenr = 1;
+	e = parse_expr();
 	if (*pos)
 		parse_error_at(NULL, "trailing garbage");
 	return e;
@@ -1377,16 +1388,40 @@ main(int argc, char *argv[])
 	while ((c = getopt(argc, argv, "Tt:v")) != -1)
 		switch (c) {
 		case 'T': Tflag = need_thr = 1; break;
-		case 't': expr = chain(expr, EXPR_AND, parse_expr(optarg)); break;
+		case 't': expr = chain(expr, EXPR_AND, parse_buf("argv", optarg)); break;
 		case 'v': vflag = 1; break;
 		default:
 			fprintf(stderr, "Usage: %s [-Tv] [-t test] [msglist ...]\n", argv0);
 			exit(1);
 		}
 
-	if (optind != argc)
-		for (c = optind; c < argc; c++)
+	if (optind != argc) {
+		for (c = optind; c < argc; c++) {
+			if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0)
+				break;
 			expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
+		}
+
+		struct stat st;
+		int fd;
+		size_t len;
+		for (; c < argc; c++) {
+			if ((fd = open(argv[c], O_RDONLY)) == -1)
+				exit(1);
+			if (fstat(fd, &st) == -1)
+				exit(1);
+			len = st.st_size;
+			char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+			if (s == MAP_FAILED) {
+				perror("mmap");
+				exit(1);
+			}
+			s[len+1] = '\0';
+			close(fd);
+			expr = chain(expr, EXPR_AND, parse_buf(argv[c], s));
+			munmap(s, len+1);
+		}
+	}
 
 	if (isatty(0))
 		i = blaze822_loop1(":", need_thr ? collect : oneline);

From 9e8989c1e638a7fc2c8359dfa76fe61f4e885963 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 15:41:29 +0100
Subject: [PATCH 11/20] mpick: better error for missing ) over multiple lines

---
 mpick.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/mpick.c b/mpick.c
index 1a6c810..71e27ee 100644
--- a/mpick.c
+++ b/mpick.c
@@ -381,11 +381,9 @@ static struct expr *
 parse_inner()
 {
 	if (token("prune")) {
-		struct expr *e = mkexpr(EXPR_PRUNE);
-		return e;
+		return mkexpr(EXPR_PRUNE);
 	} else if (token("print")) {
-		struct expr *e = mkexpr(EXPR_PRINT);
-		return e;
+		return mkexpr(EXPR_PRINT);
 	} else if (token("skip")) {
 		struct expr *e = mkexpr(EXPR_PRINT);
 		struct expr *not = mkexpr(EXPR_NOT);
@@ -396,11 +394,14 @@ parse_inner()
 		struct expr *not = mkexpr(EXPR_NOT);
 		not->a.expr = e;
 		return not;
-	} else if (token("(")) {
+	}
+	if (peek("(")) {
+		struct pos savepos = { pos, line, linenr };
+		(void) token("(");
 		struct expr *e = parse_cond();
 		if (token(")"))
 			return e;
-		parse_error("missing ) at '%.15s'", pos);
+		parse_error_at(&savepos, "unterminated (");
 		return 0;
 	} else {
 		parse_error("unknown expression at '%.15s'", pos);

From 9456402d69785ad6a2e4e5d08db0795f0c6e12dd Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Mon, 28 Jan 2019 16:22:29 +0100
Subject: [PATCH 12/20] mpick: add let expressions

---
 man/mpick.1 |  11 +++++
 mpick.c     | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 121 insertions(+), 4 deletions(-)

diff --git a/man/mpick.1 b/man/mpick.1
index 744fa24..53d5120 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -107,6 +107,8 @@ tests are given by the following EBNF:
              | prune             -- do not match further messages in thread
              | print             -- always true value
              | skip              -- always false value
+             | <let>
+             | <ident>
 
 <flagprop> ::= child | draft | flagged | info | new | parent | passed
              | replied  | seen | selected | trashed
@@ -149,6 +151,15 @@ tests are given by the following EBNF:
 
 <str>      ::= " ([^"] | "")+ "  -- use "" for a single " inside "
              | $[A-Za-z0-9_]+    -- environment variable
+
+-- let expressions evaluate the expression following the `in` keyword,
+-- the bindings are lazily evaluated.
+<let>      ::= { let <ident> = <scope> } in <scope>
+
+-- Inside the scope previously defined idents are replaced with expressions
+<scope>    ::= <expr>
+
+<ident>    ::= [A-Za-z_][A-Za-z0-9_]+
 .Ed
 .Sh EXIT STATUS
 .Ex -std
diff --git a/mpick.c b/mpick.c
index 71e27ee..3dc6660 100644
--- a/mpick.c
+++ b/mpick.c
@@ -162,6 +162,21 @@ struct pos {
 	size_t linenr;
 };
 
+struct binding {
+	char *name;
+	size_t nlen;
+	size_t refcount;
+	struct expr *expr;
+	struct binding *next;
+};
+
+struct scope {
+	struct binding *bindings;
+	struct scope *prev;
+};
+
+struct scope *scopeq = NULL;
+
 static struct thread *thr;
 
 static char *argv0;
@@ -374,9 +389,102 @@ parse_op()
 	return 0;
 }
 
-static struct expr *parse_cmp();
+static int
+parse_ident(char **sp, size_t *lp)
+{
+	char *p = pos;
+	if (!isalpha(*pos) && *pos != '_')
+		return 0;
+	p++;
+	while (*p && (isalnum(*p) || *p == '_'))
+		p++;
+	*sp = pos;
+	*lp = p-pos;
+	pos = p;
+	ws();
+	return 1;
+}
+
+static struct expr *
+parse_binding()
+{
+	struct scope *sc;
+	struct binding *b;
+	char *s;
+	size_t l = 0;
+
+	if (parse_ident(&s, &l)) {
+		for (sc = scopeq; sc; sc = sc->prev) {
+			for (b = sc->bindings; b; b = b->next) {
+				if (b->nlen == l && strncmp(b->name, s, l) == 0) {
+					b->refcount++;
+					return b->expr;
+				}
+			}
+		}
+	}
+	// back to the start of the ident if there was one
+	pos = pos-l;
+	parse_error_at(NULL, "unknown expression");
+	return 0;
+}
+
 static struct expr *parse_cond();
 
+static struct expr *
+parse_let()
+{
+	if (!token("let"))
+		return parse_binding();
+
+	struct scope *sc;
+	char *s;
+	size_t l;
+
+	sc = xcalloc(1, sizeof (struct scope));
+	sc->prev = scopeq;
+	scopeq = sc;
+
+	struct binding *b, *bq;
+	struct expr *e;
+	bq = NULL;
+	for (;;) {
+		if (!parse_ident(&s, &l))
+			parse_error_at(NULL, "missing ident");
+		if (!token("="))
+			parse_error_at(NULL, "missing =");
+		e = parse_cond();
+		b = xcalloc(1, sizeof (struct binding));
+		b->name = s;
+		b->nlen = l;
+		b->expr = e;
+		if (!sc->bindings) sc->bindings = b;
+		if (bq) bq->next = b;
+		bq = b;
+		if (!token("let"))
+			break;
+	}
+	if (!token("in"))
+		parse_error_at(NULL, "missing `in`");
+
+	e = parse_cond();
+
+	struct binding *bs;
+	for (b = sc->bindings; b; b = bs) {
+		bs = b->next;
+		if (b->refcount == 0)
+			freeexpr(b->expr);
+		free(b);
+	}
+
+	scopeq = sc->prev;
+	free(sc);
+
+	return e;
+}
+
+static struct expr *parse_cmp();
+
 static struct expr *
 parse_inner()
 {
@@ -403,10 +511,8 @@ parse_inner()
 			return e;
 		parse_error_at(&savepos, "unterminated (");
 		return 0;
-	} else {
-		parse_error("unknown expression at '%.15s'", pos);
-		return 0;
 	}
+	return parse_let();
 }
 
 static int

From 2095a16bceeb4aec7165b1608ba73d02d2975a6a Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Tue, 29 Jan 2019 14:07:55 +0100
Subject: [PATCH 13/20] mpick: add header decodeop for better address matching

---
 man/mpick.1    |  10 ++-
 mpick.c        | 176 +++++++++++++++++++++++++++++--------------------
 t/2000-mpick.t |   4 +-
 3 files changed, 115 insertions(+), 75 deletions(-)

diff --git a/man/mpick.1 b/man/mpick.1
index 53d5120..bb7d324 100644
--- a/man/mpick.1
+++ b/man/mpick.1
@@ -103,6 +103,7 @@ tests are given by the following EBNF:
              | <flagprop>
              | <timeprop> <numop> <dur>
              | <numprop> <numop> <num>
+             | <hdrprop> <decodeop> <strop> <str>
              | <strprop> <strop> <str>
              | prune             -- do not match further messages in thread
              | print             -- always true value
@@ -139,8 +140,13 @@ tests are given by the following EBNF:
                       | T )?     -- *1024*1024*1024*1024
              | cur               -- index of cur message
 
-<strprop>  ::= from | subject | to
-             | <str>             -- header name
+<hdrprop>  ::= from | to | subject | <str>
+
+<decodeop> ::= . addr          -- match address parts
+             | . disp          -- match address display parts
+             |                 -- empty matches raw headers
+
+<strprop>  ::= path
 
 <strop>    ::= == | = | !=       -- string (in)equality
              | ===    | !===     -- case insensitive string (in)equality
diff --git a/mpick.c b/mpick.c
index 3dc6660..bff90b8 100644
--- a/mpick.c
+++ b/mpick.c
@@ -80,11 +80,12 @@ enum prop {
 	PROP_REPLIES,
 	PROP_SIZE,
 	PROP_TOTAL,
-	PROP_FROM,
-	PROP_TO,
 	PROP_INDEX,
 	PROP_DATE,
 	PROP_FLAG,
+	PROP_HDR,
+	PROP_HDR_ADDR,
+	PROP_HDR_DISP,
 };
 
 enum flags {
@@ -332,16 +333,17 @@ freeexpr(struct expr *e)
 	case EXPR_REGEX:
 	case EXPR_REGEXI:
 		switch (e->a.prop) {
-		case PROP_PATH:
-		case PROP_FROM:
-		case PROP_TO:
-			break;
-		default: free(e->a.string);
+		case PROP_PATH: break;
+		case PROP_HDR:
+		case PROP_HDR_ADDR:
+		case PROP_HDR_DISP:
+			free(e->b.string);
+		default: return;
 		}
 		if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
-			regfree(e->b.regex);
+			regfree(e->c.regex);
 		else
-			free(e->b.string);
+			free(e->c.string);
 	}
 	free(e);
 }
@@ -579,14 +581,25 @@ parse_strcmp()
 	negate = 0;
 
 	if (token("from"))
-		prop = PROP_FROM;
+		h = xstrdup("from");
 	else if (token("to"))
-		prop = PROP_TO;
+		h = xstrdup("to");
 	else if (token("subject"))
-		h = "subject";
+		h = xstrdup("subject");
+	else if (token("path"))
+		prop = PROP_PATH;
 	else if (!parse_string(&h))
 		return parse_inner();
 
+	if (!prop) {
+		if (token(".")) {
+			if (token("addr"))      prop = PROP_HDR_ADDR;
+			else if (token("disp")) prop = PROP_HDR_DISP;
+			else parse_error_at(NULL, "unknown decode parameter");
+		} else
+			prop = PROP_HDR;
+	}
+
 	if (token("~~~"))
 		op = EXPR_GLOBI;
 	else if (token("~~"))
@@ -624,35 +637,29 @@ parse_strcmp()
 
 	int r = 0;
 	struct expr *e = mkexpr(op);
-
-	if (prop)
-		e->a.prop = prop;
-	else
-		e->a.string = h;
-
-	if (prop == PROP_FROM || prop == PROP_TO) {
-		char *disp, *addr;
-		blaze822_addr(s, &disp, &addr);
-		if (!disp && !addr)
-			parse_error_at(NULL, "invalid address");
-		free(s);
-		s = xstrdup((disp) ? disp : addr);
-		e->extra = (disp) ? 0 : 1;
+	e->a.prop = prop;
+	switch (prop) {
+	case PROP_HDR:
+	case PROP_HDR_ADDR:
+	case PROP_HDR_DISP:
+		e->b.string = h;
+		break;
+	case PROP_PATH: break;
 	}
 
 	if (op == EXPR_REGEX || op == EXPR_REGEXI) {
-		e->b.regex = malloc(sizeof (regex_t));
-		r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB |
+		e->c.regex = malloc(sizeof (regex_t));
+		r = regcomp(e->c.regex, s, REG_EXTENDED | REG_NOSUB |
 		    (op == EXPR_REGEXI ? REG_ICASE : 0));
 		if (r != 0) {
 			char msg[256];
-			regerror(r, e->b.regex, msg, sizeof msg);
+			regerror(r, e->c.regex, msg, sizeof msg);
 			parse_error("invalid regex '%s': %s", s, msg);
 			exit(2);
 		}
 		free(s);
 	} else {
-		e->b.string = s;
+		e->c.string = s;
 	}
 
 	if (negate) {
@@ -996,12 +1003,13 @@ parse_msglist(char *s)
 	case '/':
 		s++;
 		e1 = mkexpr(EXPR_REGEXI);
-		e1->a.string = xstrdup("subject");
-		e1->b.regex = malloc(sizeof (regex_t));
-		r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+		e1->a.prop = PROP_HDR;
+		e1->b.string = xstrdup("subject");
+		e1->c.regex = malloc(sizeof (regex_t));
+		r = regcomp(e1->c.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
 		if (r != 0) {
 			char msg[256];
-			regerror(r, e1->b.regex, msg, sizeof msg);
+			regerror(r, e1->c.regex, msg, sizeof msg);
 			parse_error("invalid regex '%s': %s", s, msg);
 		}
 		return e1;
@@ -1067,14 +1075,14 @@ parse_msglist(char *s)
 			d = (disp) ? disp : addr;
 
 			e1 = mkexpr(EXPR_REGEXI);
-			e1->a.prop = PROP_FROM;
-			e1->b.regex = malloc(sizeof (regex_t));
-			e1->extra = (disp) ? 0 : 1;
+			e1->a.prop = (disp) ? PROP_HDR_DISP : PROP_HDR_ADDR;
+			e1->b.string = xstrdup("from");
+			e1->c.regex = malloc(sizeof (regex_t));
 
-			r = regcomp(e1->b.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE);
+			r = regcomp(e1->c.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE);
 			if (r != 0) {
 				char msg[256];
-				regerror(r, e1->b.regex, msg, sizeof msg);
+				regerror(r, e1->c.regex, msg, sizeof msg);
 				parse_error("invalid regex '%s': %s", d, msg);
 			}
 
@@ -1106,54 +1114,64 @@ msg_date(struct mailinfo *m)
 }
 
 char *
-msg_hdr(struct mailinfo *m, const char *h)
+msg_hdr(char **s, const char *h, struct mailinfo *m)
 {
 	static char hdrbuf[4096];
 
 	if (!m->msg && m->fpath) {
 		if (!(m->msg = blaze822(m->fpath))) {
 			m->fpath = NULL;
-			*hdrbuf = 0;
-			return hdrbuf;
+			return NULL;
 		}
 	}
 
+	// XXX: only return one header for now
+	if (*s)
+		return NULL;
+
 	char *b;
-	if (!m->msg || !(b = blaze822_chdr(m->msg, h))) {
-		*hdrbuf = 0;
-		return hdrbuf;
-	}
+	if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
+		return NULL;
+	*s = b;
 
 	blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8");
 	return hdrbuf;
 }
 
 char *
-msg_addr(struct mailinfo *m, char *h, int t)
+msg_hdr_addr(char **s, const char *h, struct mailinfo *m, int rdisp)
 {
 	if (!m->msg && m->fpath) {
 		if (!(m->msg = blaze822(m->fpath))) {
 			m->fpath = NULL;
-			return "";
+			return NULL;
 		}
 	}
 
-	char *b;
-	if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0)
-		return "";
+	char *b = *s;
+	if (!b) {
+		if (!m->msg || !(b = blaze822_chdr(m->msg, h)))
+			return NULL;
+	}
 
 	char *disp, *addr;
-	blaze822_addr(b, &disp, &addr);
+	*s = blaze822_addr(b, &disp, &addr);
 
-	if (t) {
-		if (!addr)
-			return "";
-		return addr;
-	} else {
-		if (!disp)
-			return "";
+	if (rdisp)
 		return disp;
-	}
+	return addr;
+}
+
+char *
+msg_hdr_address(char **s, const char *h, struct mailinfo *m)
+{
+	return msg_hdr_addr(s, h, m, 0);
+}
+
+char *
+msg_hdr_display(char **s, const char *h, struct mailinfo *m)
+{
+	return msg_hdr_addr(s, h, m, 1);
 }
 
 FILE *
@@ -1283,21 +1301,34 @@ eval(struct expr *e, struct mailinfo *m)
 	case EXPR_GLOBI:
 	case EXPR_REGEX:
 	case EXPR_REGEXI: {
-		const char *s;
+		const char *s = NULL;
+		char *p = NULL;
+		char *(*fn)(char **, const char *, struct mailinfo *) = 0;
+		int rv = 0;
 		switch (e->a.prop) {
+		case PROP_HDR: fn = msg_hdr; break;
+		case PROP_HDR_ADDR: fn = msg_hdr_address; break;
+		case PROP_HDR_DISP: fn = msg_hdr_display; break;
 		case PROP_PATH: s = m->fpath ? m->fpath : ""; break;
-		case PROP_FROM: s = msg_addr(m, "from", e->extra); break;
-		case PROP_TO: s = msg_addr(m, "to", e->extra); break;
-		default: s = msg_hdr(m, e->a.string); break;
+		default: return 0;
 		}
-		switch (e->op) {
-		case EXPR_STREQ: return strcmp(e->b.string, s) == 0;
-		case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0;
-		case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0;
-		case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0;
-		case EXPR_REGEX:
-		case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0;
+		for (;;) {
+			if (fn && !(s = fn(&p, e->b.string, m)))
+				break;
+			switch (e->op) {
+			case EXPR_STREQ: rv = strcmp(e->c.string, s) == 0; break;
+			case EXPR_STREQI: rv = strcasecmp(e->c.string, s) == 0; break;
+			case EXPR_GLOB: rv = fnmatch(e->c.string, s, 0) == 0; break;
+			case EXPR_GLOBI:
+				rv = fnmatch(e->c.string, s, FNM_CASEFOLD) == 0; break;
+			case EXPR_REGEX:
+			case EXPR_REGEXI:
+				rv = regexec(e->c.regex, s, 0, 0, 0) == 0;
+				break;
+			}
+			if (!fn || rv) return rv;
 		}
+		return 0;
 	}
 	}
 	return 0;
@@ -1504,8 +1535,9 @@ main(int argc, char *argv[])
 
 	if (optind != argc) {
 		for (c = optind; c < argc; c++) {
-			if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0)
+			if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) {
 				break;
+			}
 			expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
 		}
 
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 8e32aec..9af444d 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
 #!/bin/sh -e
 cd ${0%/*}
 . ./lib.sh
-plan 13
+plan 15
 
 rm -rf test.dir
 mkdir test.dir
@@ -66,5 +66,7 @@ check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l'
 check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l'
 check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l'
 check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l'
+check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
+check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
 
 )

From 8c2c1640eb1d64f351e63e49eee28e9d9f82a88c Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Fri, 8 Feb 2019 19:48:02 +0100
Subject: [PATCH 14/20] t/2000-mpick.t: add tests for all the new features

---
 t/2000-mpick.t | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 9af444d..802ea83 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
 #!/bin/sh -e
 cd ${0%/*}
 . ./lib.sh
-plan 15
+plan 26
 
 rm -rf test.dir
 mkdir test.dir
@@ -61,6 +61,12 @@ Greetings
 #application/pdf ../../mshow
 !
 
+cat <<! >shebang
+#!$(command -v mpick)
+from.addr == "peter@example.org" && from.disp == "Peter Example"
+!
+chmod +x shebang
+
 check 'search subject' 'mlist inbox | mpick /wow | grep -q inbox/cur/9:2,'
 check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l'
 check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l'
@@ -68,5 +74,48 @@ check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"
 check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l'
 check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l'
 check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l'
+check_test 'shebang' -eq 2 'mlist inbox | ./shebang | wc -l'
+check_test 'ternary' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\" ? print : skip" | wc -l'
+
+check_same 'pipe command' 'mlist inbox | mpick -t "print |\"cat -n\" && skip"' 'mlist inbox | cat -n'
+check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
+check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
+check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+# random comment
+in
+  foo && bar # another comment
+!
+check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo =
+  let bar = from.disp == "Peter Example"
+  in
+    bar && from.addr == "peter@example.org"
+in
+  foo
+!
+check_test 'let expression nested' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = foo && subject =~ "wow"
+in
+  bar
+!
+check_test 'let scoping' -eq 1 'mlist inbox | mpick ./expr | wc -l'
+
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+in
+  foo |"sed ""s/^/1:&/""" && bar |"sed ""s/^/2:&/""" && skip
+!
+check_test 'multi redir' -eq 4 'mlist inbox | mpick ./expr | wc -l'
+check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick ./expr | cut -d: -f1 | sort -u | wc -l'
 
 )

From 4b413aa6a192a81bd080cad47c393d843ef0a33c Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Thu, 6 Jun 2019 21:48:24 +0200
Subject: [PATCH 15/20] mpick: always point to malloc memory in parse_string

---
 mpick.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mpick.c b/mpick.c
index bff90b8..45c350d 100644
--- a/mpick.c
+++ b/mpick.c
@@ -544,7 +544,7 @@ parse_string(char **s)
 			buf[len] = 0;
 		pos++;
 		ws();
-		*s = buf ? buf : "";
+		*s = buf ? buf : xstrdup("");
 		return 1;
 	} else if (*pos == '$') {
 		char t;
@@ -559,7 +559,7 @@ parse_string(char **s)
 		*pos = 0;
 		*s = getenv(e);
 		if (!*s)
-			*s = "";
+			*s = xstrdup("");
 		*pos = t;
 		ws();
 		return 1;

From 1d662aee124da84e80aa257b4161191c712e0e72 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Thu, 6 Jun 2019 21:48:57 +0200
Subject: [PATCH 16/20] mpick: fix memory leak in parse_dur

---
 mpick.c | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/mpick.c b/mpick.c
index 45c350d..1e3d0de 100644
--- a/mpick.c
+++ b/mpick.c
@@ -791,20 +791,20 @@ parse_dur(int64_t *n)
 			parse_error("can't stat file '%s': %s",
 			    s, strerror(errno));
 		*n = st.st_mtime;
-		return 1;
+		goto ret;
 	}
 
 	struct tm tm = { 0 };
 	r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm);
 	if (r && !*r) {
 		*n = mktime(&tm);
-		return 1;
+		goto ret;
 	}
 	r = strptime(s, "%Y-%m-%d", &tm);
 	if (r && !*r) {
 		tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
 		*n = mktime(&tm);
-		return 1;
+		goto ret;
 	}
 	r = strptime(s, "%H:%M:%S", &tm);
 	if (r && !*r) {
@@ -813,7 +813,7 @@ parse_dur(int64_t *n)
 		tm.tm_mon = tmnow->tm_mon;
 		tm.tm_mday = tmnow->tm_mday;
 		*n = mktime(&tm);
-		return 1;
+		goto ret;
 	}
 	r = strptime(s, "%H:%M", &tm);
 	if (r && !*r) {
@@ -823,39 +823,40 @@ parse_dur(int64_t *n)
 		tm.tm_mday = tmnow->tm_mday;
 		tm.tm_sec = 0;
 		*n = mktime(&tm);
-		return 1;
+		goto ret;
 	}
 
 	if (*s == '-') {
-		s++;
-
-		errno = 0;
 		int64_t d;
-		d = strtol(s, &r, 10);
+		errno = 0;
+		d = strtol(s+1, &r, 10);
 		if (errno == 0 && r[0] == 'd' && !r[1]) {
 			struct tm *tmnow = localtime(&now);
 			tmnow->tm_mday -= d;
 			tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0;
 			*n = mktime(tmnow);
-			return 1;
+			goto ret;
 		}
 		if (errno == 0 && r[0] == 'h' && !r[1]) {
 			*n = now - (d*60*60);
-			return 1;
+			goto ret;
 		}
 		if (errno == 0 && r[0] == 'm' && !r[1]) {
 			*n = now - (d*60);
-			return 1;
+			goto ret;
 		}
 		if (errno == 0 && r[0] == 's' && !r[1]) {
 			*n = now - d;
-			return 1;
+			goto ret;
 		}
-		parse_error("invalid relative time format '%s'", s-1);
+		parse_error("invalid relative time format '%s'", s);
 	}
 
 	parse_error("invalid time format '%s'", s);
 	return 0;
+ret:
+	free(s);
+	return 1;
 }
 
 static struct expr *

From a6ea4cb92bee43fe9ac66ad476355cff958491a9 Mon Sep 17 00:00:00 2001
From: Duncaen <mail@duncano.de>
Date: Fri, 7 Jun 2019 01:24:49 +0200
Subject: [PATCH 17/20] mpick: use slurp instead of mmaping files

---
 mpick.c | 21 +++++++--------------
 1 file changed, 7 insertions(+), 14 deletions(-)

diff --git a/mpick.c b/mpick.c
index 1e3d0de..adf6b09 100644
--- a/mpick.c
+++ b/mpick.c
@@ -1542,24 +1542,17 @@ main(int argc, char *argv[])
 			expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
 		}
 
-		struct stat st;
-		int fd;
-		size_t len;
 		for (; c < argc; c++) {
-			if ((fd = open(argv[c], O_RDONLY)) == -1)
-				exit(1);
-			if (fstat(fd, &st) == -1)
-				exit(1);
-			len = st.st_size;
-			char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
-			if (s == MAP_FAILED) {
-				perror("mmap");
+			char *s;
+			off_t len;
+			int r = slurp(argv[c], &s, &len);
+			if (r != 0) {
+				fprintf(stderr, "%s: error opening file '%s': %s\n",
+					argv0, argv[c], strerror(r));
 				exit(1);
 			}
-			s[len+1] = '\0';
-			close(fd);
 			expr = chain(expr, EXPR_AND, parse_buf(argv[c], s));
-			munmap(s, len+1);
+			free(s);
 		}
 	}
 

From 475fe4447ba5fb03a26bcc9893e5a2b7e21e5ae8 Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 15:30:53 +0100
Subject: [PATCH 18/20] mpick: fix memory leaks

---
 mpick.c | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/mpick.c b/mpick.c
index adf6b09..d72301c 100644
--- a/mpick.c
+++ b/mpick.c
@@ -68,6 +68,7 @@ enum op {
 	EXPR_TYPE,
 	EXPR_ALLSET,
 	EXPR_ANYSET,
+	EXPR_BINDING,
 };
 
 enum prop {
@@ -118,6 +119,7 @@ struct expr {
 		int64_t num;
 		regex_t *regex;
 		enum var var;
+		struct binding *binding;
 	} a, b, c;
 	int extra;
 };
@@ -321,6 +323,13 @@ freeexpr(struct expr *e)
 	case EXPR_NOT:
 		freeexpr(e->a.expr);
 		break;
+	case EXPR_BINDING:
+		e->a.binding->refcount -= 1;
+		if (e->a.binding->refcount == 0) {
+			freeexpr(e->a.binding->expr);
+			free(e->a.binding);
+		}
+		break;
 	case EXPR_REDIR_FILE:
 	case EXPR_REDIR_PIPE:
 		free(e->a.string);
@@ -338,12 +347,12 @@ freeexpr(struct expr *e)
 		case PROP_HDR_ADDR:
 		case PROP_HDR_DISP:
 			free(e->b.string);
-		default: return;
 		}
 		if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
 			regfree(e->c.regex);
 		else
 			free(e->c.string);
+		break;
 	}
 	free(e);
 }
@@ -419,8 +428,10 @@ parse_binding()
 		for (sc = scopeq; sc; sc = sc->prev) {
 			for (b = sc->bindings; b; b = b->next) {
 				if (b->nlen == l && strncmp(b->name, s, l) == 0) {
+					struct expr *e = mkexpr(EXPR_BINDING);
+					e->a.binding = b;
 					b->refcount++;
-					return b->expr;
+					return e;
 				}
 			}
 		}
@@ -460,9 +471,12 @@ parse_let()
 		b->name = s;
 		b->nlen = l;
 		b->expr = e;
-		if (!sc->bindings) sc->bindings = b;
-		if (bq) bq->next = b;
-		bq = b;
+		if (bq) {
+			bq->next = b;
+			bq = b;
+		} else {
+			bq = sc->bindings = b;
+		}
 		if (!token("let"))
 			break;
 	}
@@ -474,8 +488,9 @@ parse_let()
 	struct binding *bs;
 	for (b = sc->bindings; b; b = bs) {
 		bs = b->next;
-		if (b->refcount == 0)
-			freeexpr(b->expr);
+		if (b->refcount != 0)
+			continue;
+		freeexpr(b->expr);
 		free(b);
 	}
 
@@ -1224,7 +1239,8 @@ eval(struct expr *e, struct mailinfo *m)
 			: eval(e->c.expr, m);
 	case EXPR_NOT:
 		return !eval(e->a.expr, m);
-		return 1;
+	case EXPR_BINDING:
+		return eval(e->a.binding->expr, m);
 	case EXPR_PRUNE:
 		prune = 1;
 		return 1;

From 89be0ace74d43a06d2e2200422ec33aedb73e53c Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 15:47:29 +0100
Subject: [PATCH 19/20] t/2000-mpick.t: check for double free with bindings

---
 t/2000-mpick.t | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 802ea83..3cff87e 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
 #!/bin/sh -e
 cd ${0%/*}
 . ./lib.sh
-plan 26
+plan 27
 
 rm -rf test.dir
 mkdir test.dir
@@ -91,6 +91,15 @@ in
 !
 check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l'
 
+cat <<! >expr
+let foo = from.addr == "peter@example.org"
+let bar = from.disp == "Peter Example"
+# random comment
+in
+  foo && foo
+!
+check_test 'let expression double free' -eq 2 'mlist inbox | mpick ./expr | wc -l'
+
 cat <<! >expr
 let foo =
   let bar = from.disp == "Peter Example"

From 65016bed8bef29ae2bebed958a763e7257e57652 Mon Sep 17 00:00:00 2001
From: Duncan Overbruck <mail@duncano.de>
Date: Wed, 5 Feb 2020 16:07:41 +0100
Subject: [PATCH 20/20] mpick: improve unknown binding error and add test case

---
 mpick.c        | 6 +++---
 t/2000-mpick.t | 5 ++++-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/mpick.c b/mpick.c
index d72301c..5fcb308 100644
--- a/mpick.c
+++ b/mpick.c
@@ -423,6 +423,7 @@ parse_binding()
 	struct binding *b;
 	char *s;
 	size_t l = 0;
+	struct pos savepos = { pos, line, linenr };
 
 	if (parse_ident(&s, &l)) {
 		for (sc = scopeq; sc; sc = sc->prev) {
@@ -436,9 +437,8 @@ parse_binding()
 			}
 		}
 	}
-	// back to the start of the ident if there was one
-	pos = pos-l;
-	parse_error_at(NULL, "unknown expression");
+
+	parse_error_at(&savepos, "unknown expression");
 	return 0;
 }
 
diff --git a/t/2000-mpick.t b/t/2000-mpick.t
index 3cff87e..0b1296c 100755
--- a/t/2000-mpick.t
+++ b/t/2000-mpick.t
@@ -1,7 +1,7 @@
 #!/bin/sh -e
 cd ${0%/*}
 . ./lib.sh
-plan 27
+plan 28
 
 rm -rf test.dir
 mkdir test.dir
@@ -82,6 +82,9 @@ check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat
 check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox'
 check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox'
 
+check_same 'unknown ident' 'mlist inbox | mpick -t "let x = x in x" 2>&1' \
+	"echo \"mpick: parse error: argv:1:9: unknown expression at 'x in x'\""
+
 cat <<! >expr
 let foo = from.addr == "peter@example.org"
 let bar = from.disp == "Peter Example"

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2020-05-15 16:37 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <gh-mailinglist-notifications-fa6558a0-26e0-48f6-803f-f5a8af34f6a8-mblaze-150@inbox.vuxu.org>
2020-05-15 16:37 ` [PR PATCH] [Updated] mpick craziness Duncaen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).