summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--man/mpick.111
-rw-r--r--mpick.c114
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,10 +389,103 @@ 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()
 {
 	if (token("prune")) {
@@ -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