json: Move from lib to include/openvswitch.
[cascardo/ovs.git] / ovn / lib / expr.c
index 71d817b..1c38b99 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Nicira, Inc.
+ * Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #include <config.h>
+#include "byte-order.h"
 #include "expr.h"
-#include "dynamic-string.h"
-#include "json.h"
+#include "openvswitch/json.h"
 #include "lex.h"
-#include "match.h"
-#include "ofp-actions.h"
-#include "shash.h"
+#include "logical-fields.h"
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/match.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/vlog.h"
+#include "openvswitch/shash.h"
 #include "simap.h"
 #include "sset.h"
-#include "openvswitch/vlog.h"
+#include "util.h"
 
 VLOG_DEFINE_THIS_MODULE(expr);
 \f
@@ -123,7 +126,7 @@ expr_create_andor(enum expr_type type)
 {
     struct expr *e = xmalloc(sizeof *e);
     e->type = type;
-    list_init(&e->andor);
+    ovs_list_init(&e->andor);
     return e;
 }
 
@@ -146,19 +149,19 @@ expr_combine(enum expr_type type, struct expr *a, struct expr *b)
         return a;
     } else if (a->type == type) {
         if (b->type == type) {
-            list_splice(&a->andor, b->andor.next, &b->andor);
+            ovs_list_splice(&a->andor, b->andor.next, &b->andor);
             free(b);
         } else {
-            list_push_back(&a->andor, &b->node);
+            ovs_list_push_back(&a->andor, &b->node);
         }
         return a;
     } else if (b->type == type) {
-        list_push_front(&b->andor, &a->node);
+        ovs_list_push_front(&b->andor, &a->node);
         return b;
     } else {
         struct expr *e = expr_create_andor(type);
-        list_push_back(&e->andor, &a->node);
-        list_push_back(&e->andor, &b->node);
+        ovs_list_push_back(&e->andor, &a->node);
+        ovs_list_push_back(&e->andor, &b->node);
         return e;
     }
 }
@@ -170,10 +173,10 @@ expr_insert_andor(struct expr *andor, struct expr *before, struct expr *new)
         if (andor->type == EXPR_T_AND) {
             /* Conjunction junction, what's your function? */
         }
-        list_splice(&before->node, new->andor.next, &new->andor);
+        ovs_list_splice(&before->node, new->andor.next, &new->andor);
         free(new);
     } else {
-        list_insert(&before->node, &new->node);
+        ovs_list_insert(&before->node, &new->node);
     }
 }
 
@@ -224,18 +227,18 @@ expr_fix_andor(struct expr *expr, bool short_circuit)
                 expr_destroy(expr);
                 return expr_create_boolean(short_circuit);
             } else {
-                list_remove(&sub->node);
+                ovs_list_remove(&sub->node);
                 expr_destroy(sub);
             }
         }
     }
 
-    if (list_is_short(&expr->andor)) {
-        if (list_is_empty(&expr->andor)) {
+    if (ovs_list_is_short(&expr->andor)) {
+        if (ovs_list_is_empty(&expr->andor)) {
             free(expr);
             return expr_create_boolean(!short_circuit);
         } else {
-            sub = expr_from_node(list_front(&expr->andor));
+            sub = expr_from_node(ovs_list_front(&expr->andor));
             free(expr);
             return sub;
         }
@@ -403,59 +406,17 @@ expr_print(const struct expr *e)
 \f
 /* Parsing. */
 
-/* Type of a "union expr_constant" or "struct expr_constant_set". */
-enum expr_constant_type {
-    EXPR_C_INTEGER,
-    EXPR_C_STRING
-};
-
-/* A string or integer constant (one must know which from context). */
-union expr_constant {
-    /* Integer constant.
-     *
-     * The width of a constant isn't always clear, e.g. if you write "1",
-     * there's no way to tell whether you mean for that to be a 1-bit constant
-     * or a 128-bit constant or somewhere in between. */
-    struct {
-        union mf_subvalue value;
-        union mf_subvalue mask; /* Only initialized if 'masked'. */
-        bool masked;
-
-        enum lex_format format; /* From the constant's lex_token. */
-    };
-
-    /* Null-terminated string constant. */
-    char *string;
-};
-
-/* A collection of "union expr_constant"s of the same type. */
-struct expr_constant_set {
-    union expr_constant *values;  /* Constants. */
-    size_t n_values;              /* Number of constants. */
-    enum expr_constant_type type; /* Type of the constants. */
-    bool in_curlies;              /* Whether the constants were in {}. */
-};
-
-/* A reference to a symbol or a subfield of a symbol.
- *
- * For string fields, ofs and n_bits are 0. */
-struct expr_field {
-    const struct expr_symbol *symbol; /* The symbol. */
-    int ofs;                          /* Starting bit offset. */
-    int n_bits;                       /* Number of bits. */
-};
-
 /* Context maintained during expr_parse(). */
 struct expr_context {
     struct lexer *lexer;        /* Lexer for pulling more tokens. */
     const struct shash *symtab; /* Symbol table. */
+    const struct shash *macros; /* Table of macros. */
     char *error;                /* Error, if any, otherwise NULL. */
     bool not;                   /* True inside odd number of NOT operators. */
 };
 
 struct expr *expr_parse__(struct expr_context *);
 static void expr_not(struct expr *);
-static void expr_constant_set_destroy(struct expr_constant_set *);
 static bool parse_field(struct expr_context *, struct expr_field *);
 
 static bool
@@ -668,16 +629,11 @@ exit:
 static bool
 expr_get_int(struct expr_context *ctx, int *value)
 {
-    if (ctx->lexer->token.type == LEX_T_INTEGER
-        && ctx->lexer->token.format == LEX_F_DECIMAL
-        && ntohll(ctx->lexer->token.value.integer) <= INT_MAX) {
-        *value = ntohll(ctx->lexer->token.value.integer);
-        lexer_get(ctx->lexer);
-        return true;
-    } else {
+    bool ok = lexer_get_int(ctx->lexer, value);
+    if (!ok) {
         expr_syntax_error(ctx, "expecting small integer.");
-        return false;
     }
+    return ok;
 }
 
 static bool
@@ -774,6 +730,33 @@ assign_constant_set_type(struct expr_context *ctx,
     }
 }
 
+static bool
+parse_macros(struct expr_context *ctx, struct expr_constant_set *cs,
+             size_t *allocated_values)
+{
+    struct expr_constant_set *addr_set
+        = shash_find_data(ctx->macros, ctx->lexer->token.s);
+    if (!addr_set) {
+        expr_syntax_error(ctx, "expecting address set name.");
+        return false;
+    }
+
+    if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) {
+        return false;
+    }
+
+    size_t n_values = cs->n_values + addr_set->n_values;
+    if (n_values >= *allocated_values) {
+        cs->values = xrealloc(cs->values, n_values * sizeof *cs->values);
+        *allocated_values = n_values;
+    }
+    for (size_t i = 0; i < addr_set->n_values; i++) {
+        cs->values[cs->n_values++] = addr_set->values[i];
+    }
+
+    return true;
+}
+
 static bool
 parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
                size_t *allocated_values)
@@ -805,6 +788,12 @@ parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
         }
         lexer_get(ctx->lexer);
         return true;
+    } else if (ctx->lexer->token.type == LEX_T_MACRO) {
+        if (!parse_macros(ctx, cs, allocated_values)) {
+            return false;
+        }
+        lexer_get(ctx->lexer);
+        return true;
     } else {
         expr_syntax_error(ctx, "expecting constant.");
         return false;
@@ -841,7 +830,7 @@ parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs)
     return ok;
 }
 
-static void
+void
 expr_constant_set_destroy(struct expr_constant_set *cs)
 {
     if (cs) {
@@ -854,6 +843,69 @@ expr_constant_set_destroy(struct expr_constant_set *cs)
     }
 }
 
+/* Adds a macro named 'name' to 'macros', replacing any existing macro with the
+ * given name. */
+void
+expr_macros_add(struct shash *macros, const char *name,
+                const char *const *values, size_t n_values)
+{
+    /* Replace any existing entry for this name. */
+    expr_macros_remove(macros, name);
+
+    struct expr_constant_set *cs = xzalloc(sizeof *cs);
+    cs->type = EXPR_C_INTEGER;
+    cs->in_curlies = true;
+    cs->n_values = 0;
+    cs->values = xmalloc(n_values * sizeof *cs->values);
+    for (size_t i = 0; i < n_values; i++) {
+        /* Use the lexer to convert each macro into the proper
+         * integer format. */
+        struct lexer lex;
+        lexer_init(&lex, values[i]);
+        lexer_get(&lex);
+        if (lex.token.type != LEX_T_INTEGER
+            && lex.token.type != LEX_T_MASKED_INTEGER) {
+            VLOG_WARN("Invalid address set entry: '%s', token type: %d",
+                      values[i], lex.token.type);
+        } else {
+            union expr_constant *c = &cs->values[cs->n_values++];
+            c->value = lex.token.value;
+            c->format = lex.token.format;
+            c->masked = lex.token.type == LEX_T_MASKED_INTEGER;
+            if (c->masked) {
+                c->mask = lex.token.mask;
+            }
+        }
+        lexer_destroy(&lex);
+    }
+
+    shash_add(macros, name, cs);
+}
+
+void
+expr_macros_remove(struct shash *macros, const char *name)
+{
+    struct expr_constant_set *cs = shash_find_and_delete(macros, name);
+    if (cs) {
+        expr_constant_set_destroy(cs);
+        free(cs);
+    }
+}
+
+/* Destroy all contents of 'macros'. */
+void
+expr_macros_destroy(struct shash *macros)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, macros) {
+        struct expr_constant_set *cs = node->data;
+
+        shash_delete(macros, node);
+        expr_constant_set_destroy(cs);
+    }
+}
+
 static struct expr *
 expr_parse_primary(struct expr_context *ctx, bool *atomic)
 {
@@ -1026,15 +1078,12 @@ expr_parse__(struct expr_context *ctx)
  * The caller must eventually free the returned expression (with
  * expr_destroy()) or error (with free()). */
 struct expr *
-expr_parse(struct lexer *lexer, const struct shash *symtab, char **errorp)
+expr_parse(struct lexer *lexer, const struct shash *symtab,
+           const struct shash *macros, char **errorp)
 {
-    struct expr_context ctx;
-
-    ctx.lexer = lexer;
-    ctx.symtab = symtab;
-    ctx.error = NULL;
-    ctx.not = false;
-
+    struct expr_context ctx = { .lexer = lexer,
+                                .symtab = symtab,
+                                .macros = macros };
     struct expr *e = expr_parse__(&ctx);
     *errorp = ctx.error;
     ovs_assert((ctx.error != NULL) != (e != NULL));
@@ -1043,14 +1092,15 @@ expr_parse(struct lexer *lexer, const struct shash *symtab, char **errorp)
 
 /* Like expr_parse(), but the expression is taken from 's'. */
 struct expr *
-expr_parse_string(const char *s, const struct shash *symtab, char **errorp)
+expr_parse_string(const char *s, const struct shash *symtab,
+                  const struct shash *macros, char **errorp)
 {
     struct lexer lexer;
     struct expr *expr;
 
     lexer_init(&lexer, s);
     lexer_get(&lexer);
-    expr = expr_parse(&lexer, symtab, errorp);
+    expr = expr_parse(&lexer, symtab, macros, errorp);
     if (!*errorp && lexer.token.type != LEX_T_END) {
         *errorp = xstrdup("Extra tokens at end of input.");
         expr_destroy(expr);
@@ -1111,12 +1161,7 @@ parse_field_from_string(const char *s, const struct shash *symtab,
     lexer_init(&lexer, s);
     lexer_get(&lexer);
 
-    struct expr_context ctx;
-    ctx.lexer = &lexer;
-    ctx.symtab = symtab;
-    ctx.error = NULL;
-    ctx.not = false;
-
+    struct expr_context ctx = { .lexer = &lexer, .symtab = symtab };
     bool ok = parse_field(&ctx, field);
     if (!ok) {
         *errorp = ctx.error;
@@ -1206,7 +1251,7 @@ expr_get_level(const struct expr *expr)
 static enum expr_level
 expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
 {
-    struct expr *expr = expr_parse_string(s, symtab, errorp);
+    struct expr *expr = expr_parse_string(s, symtab, NULL, errorp);
     enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
     expr_destroy(expr);
     return level;
@@ -1273,7 +1318,7 @@ expr_clone_andor(struct expr *expr)
 
     LIST_FOR_EACH (sub, node, &expr->andor) {
         struct expr *new_sub = expr_clone(sub);
-        list_push_back(&new->andor, &new_sub->node);
+        ovs_list_push_back(&new->andor, &new_sub->node);
     }
     return new;
 }
@@ -1317,7 +1362,7 @@ expr_destroy(struct expr *expr)
     case EXPR_T_AND:
     case EXPR_T_OR:
         LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            list_remove(&sub->node);
+            ovs_list_remove(&sub->node);
             expr_destroy(sub);
         }
         break;
@@ -1349,7 +1394,7 @@ parse_and_annotate(const char *s, const struct shash *symtab,
     char *error;
     struct expr *expr;
 
-    expr = expr_parse_string(s, symtab, &error);
+    expr = expr_parse_string(s, symtab, NULL, &error);
     if (expr) {
         expr = expr_annotate__(expr, symtab, nesting, &error);
     }
@@ -1381,7 +1426,7 @@ expr_annotate_cmp(struct expr *expr, const struct shash *symtab,
 
     struct annotation_nesting an;
     an.symbol = symbol;
-    list_push_back(nesting, &an.node);
+    ovs_list_push_back(nesting, &an.node);
 
     struct expr *prereqs = NULL;
     if (symbol->prereqs) {
@@ -1423,13 +1468,13 @@ expr_annotate_cmp(struct expr *expr, const struct shash *symtab,
         }
     }
 
-    list_remove(&an.node);
+    ovs_list_remove(&an.node);
     return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr;
 
 error:
     expr_destroy(expr);
     expr_destroy(prereqs);
-    list_remove(&an.node);
+    ovs_list_remove(&an.node);
     return NULL;
 }
 
@@ -1446,7 +1491,7 @@ expr_annotate__(struct expr *expr, const struct shash *symtab,
         struct expr *sub, *next;
 
         LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            list_remove(&sub->node);
+            ovs_list_remove(&sub->node);
             struct expr *new_sub = expr_annotate__(sub, symtab,
                                                    nesting, errorp);
             if (!new_sub) {
@@ -1480,7 +1525,10 @@ expr_annotate__(struct expr *expr, const struct shash *symtab,
  *     - Expands references to predicate symbols, by replacing them by the
  *       expressions that they expand to.
  *
- * In each case, annotation occurs recursively as necessary. */
+ * In each case, annotation occurs recursively as necessary.
+ *
+ * On failure, returns NULL and sets '*errorp' to an explanatory error
+ * message, which the caller must free. */
 struct expr *
 expr_annotate(struct expr *expr, const struct shash *symtab, char **errorp)
 {
@@ -1569,7 +1617,7 @@ expr_simplify(struct expr *expr)
     case EXPR_T_AND:
     case EXPR_T_OR:
         LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-            list_remove(&sub->node);
+            ovs_list_remove(&sub->node);
             expr_insert_andor(expr, next, expr_simplify(sub));
         }
         return expr_fix(expr);
@@ -1634,8 +1682,8 @@ compare_expr_sort(const void *a_, const void *b_)
         enum expr_type b_type = a->expr->type;
         return a_type < b_type ? -1 : a_type > b_type;
     } else if (a->type == EXPR_T_AND || a->type == EXPR_T_OR) {
-        size_t a_len = list_size(&a->expr->andor);
-        size_t b_len = list_size(&b->expr->andor);
+        size_t a_len = ovs_list_size(&a->expr->andor);
+        size_t b_len = ovs_list_size(&b->expr->andor);
         return a_len < b_len ? -1 : a_len > b_len;
     } else {
         return 0;
@@ -1663,7 +1711,7 @@ disjunction_matches_string(const struct expr *or, const char *s)
 static struct expr *
 crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
 {
-    ovs_assert(!list_is_short(&expr->andor));
+    ovs_assert(!ovs_list_is_short(&expr->andor));
 
     struct expr *singleton = NULL;
 
@@ -1671,12 +1719,12 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
      * EXPR_T_OR with EXPR_T_CMP subexpressions. */
     struct expr *sub, *next = NULL;
     LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        list_remove(&sub->node);
+        ovs_list_remove(&sub->node);
         struct expr *new = crush_cmps(sub, symbol);
         switch (new->type) {
         case EXPR_T_CMP:
             if (!singleton) {
-                list_insert(&next->node, &new->node);
+                ovs_list_insert(&next->node, &new->node);
                 singleton = new;
             } else {
                 bool match = !strcmp(new->cmp.string, singleton->cmp.string);
@@ -1690,7 +1738,7 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
         case EXPR_T_AND:
             OVS_NOT_REACHED();
         case EXPR_T_OR:
-            list_insert(&next->node, &new->node);
+            ovs_list_insert(&next->node, &new->node);
             break;
         case EXPR_T_BOOLEAN:
             if (!new->boolean) {
@@ -1712,7 +1760,7 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
                 return expr_create_boolean(false);
             }
         }
-        list_remove(&singleton->node);
+        ovs_list_remove(&singleton->node);
         expr_destroy(expr);
         return singleton;
     }
@@ -1748,8 +1796,9 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
         sub->type = EXPR_T_CMP;
         sub->cmp.symbol = symbol;
         sub->cmp.string = xstrdup(string);
-        list_push_back(&expr->andor, &sub->node);
+        ovs_list_push_back(&expr->andor, &sub->node);
     }
+    sset_destroy(&result);
     return expr_fix(expr);
 }
 
@@ -1758,7 +1807,7 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
 static struct expr *
 crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
 {
-    ovs_assert(!list_is_short(&expr->andor));
+    ovs_assert(!ovs_list_is_short(&expr->andor));
 
     union mf_subvalue value, mask;
     memset(&value, 0, sizeof value);
@@ -1766,7 +1815,7 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
 
     struct expr *sub, *next = NULL;
     LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        list_remove(&sub->node);
+        ovs_list_remove(&sub->node);
         struct expr *new = crush_cmps(sub, symbol);
         switch (new->type) {
         case EXPR_T_CMP:
@@ -1782,18 +1831,18 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
         case EXPR_T_AND:
             OVS_NOT_REACHED();
         case EXPR_T_OR:
-            list_insert(&next->node, &new->node);
+            ovs_list_insert(&next->node, &new->node);
             break;
         case EXPR_T_BOOLEAN:
             if (!new->boolean) {
                 expr_destroy(expr);
                 return new;
             }
-            free(new);
+            expr_destroy(new);
             break;
         }
     }
-    if (list_is_empty(&expr->andor)) {
+    if (ovs_list_is_empty(&expr->andor)) {
         if (is_all_zeros(&mask, sizeof mask)) {
             expr_destroy(expr);
             return expr_create_boolean(true);
@@ -1808,35 +1857,35 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
             expr_destroy(expr);
             return cmp;
         }
-    } else if (list_is_short(&expr->andor)) {
+    } else if (ovs_list_is_short(&expr->andor)) {
         /* Transform "a && (b || c || d)" into "ab || ac || ad" where "ab" is
          * computed as "a && b", etc. */
-        struct expr *disjuncts = expr_from_node(list_pop_front(&expr->andor));
+        struct expr *disjuncts = expr_from_node(ovs_list_pop_front(&expr->andor));
         struct expr *or;
 
         or = xmalloc(sizeof *or);
         or->type = EXPR_T_OR;
-        list_init(&or->andor);
+        ovs_list_init(&or->andor);
 
         ovs_assert(disjuncts->type == EXPR_T_OR);
         LIST_FOR_EACH_SAFE (sub, next, node, &disjuncts->andor) {
             ovs_assert(sub->type == EXPR_T_CMP);
-            list_remove(&sub->node);
+            ovs_list_remove(&sub->node);
             if (mf_subvalue_intersect(&value, &mask,
                                       &sub->cmp.value, &sub->cmp.mask,
                                       &sub->cmp.value, &sub->cmp.mask)) {
-                list_push_back(&or->andor, &sub->node);
+                ovs_list_push_back(&or->andor, &sub->node);
             } else {
-                free(sub);
+                expr_destroy(sub);
             }
         }
         free(disjuncts);
         free(expr);
-        if (list_is_empty(&or->andor)) {
+        if (ovs_list_is_empty(&or->andor)) {
             free(or);
             return expr_create_boolean(false);
-        } else if (list_is_short(&or->andor)) {
-            struct expr *cmp = expr_from_node(list_pop_front(&or->andor));
+        } else if (ovs_list_is_short(&or->andor)) {
+            struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor));
             free(or);
             return cmp;
         } else {
@@ -1845,14 +1894,14 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
     } else {
         /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into
          *           "(xa0b0 || xa0b1 || xa1b0 || xa1b1) && ...". */
-        struct expr *as = expr_from_node(list_pop_front(&expr->andor));
-        struct expr *bs = expr_from_node(list_pop_front(&expr->andor));
+        struct expr *as = expr_from_node(ovs_list_pop_front(&expr->andor));
+        struct expr *bs = expr_from_node(ovs_list_pop_front(&expr->andor));
         struct expr *new = NULL;
         struct expr *or;
 
         or = xmalloc(sizeof *or);
         or->type = EXPR_T_OR;
-        list_init(&or->andor);
+        ovs_list_init(&or->andor);
 
         struct expr *a;
         LIST_FOR_EACH (a, node, &as->andor) {
@@ -1877,7 +1926,7 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
                 if (mf_subvalue_intersect(&a_value, &a_mask,
                                           &b->cmp.value, &b->cmp.mask,
                                           &new->cmp.value, &new->cmp.mask)) {
-                    list_push_back(&or->andor, &new->node);
+                    ovs_list_push_back(&or->andor, &new->node);
                     new = NULL;
                 }
             }
@@ -1886,22 +1935,22 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
         expr_destroy(bs);
         free(new);
 
-        if (list_is_empty(&or->andor)) {
+        if (ovs_list_is_empty(&or->andor)) {
             expr_destroy(expr);
             free(or);
             return expr_create_boolean(false);
-        } else if (list_is_short(&or->andor)) {
-            struct expr *cmp = expr_from_node(list_pop_front(&or->andor));
+        } else if (ovs_list_is_short(&or->andor)) {
+            struct expr *cmp = expr_from_node(ovs_list_pop_front(&or->andor));
             free(or);
-            if (list_is_empty(&expr->andor)) {
+            if (ovs_list_is_empty(&expr->andor)) {
                 expr_destroy(expr);
                 return crush_cmps(cmp, symbol);
             } else {
                 return crush_cmps(expr_combine(EXPR_T_AND, cmp, expr), symbol);
             }
-        } else if (!list_is_empty(&expr->andor)) {
+        } else if (!ovs_list_is_empty(&expr->andor)) {
             struct expr *e = expr_combine(EXPR_T_AND, or, expr);
-            ovs_assert(!list_is_short(&e->andor));
+            ovs_assert(!ovs_list_is_short(&e->andor));
             return crush_cmps(e, symbol);
         } else {
             expr_destroy(expr);
@@ -1945,7 +1994,7 @@ crush_or(struct expr *expr, const struct expr_symbol *symbol)
      * OR-expression entirely; if so, return the result.  Otherwise, 'expr'
      * is now a disjunction of cmps over the same symbol. */
     LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
-        list_remove(&sub->node);
+        ovs_list_remove(&sub->node);
         expr_insert_andor(expr, next, crush_cmps(sub, symbol));
     }
     expr = expr_fix(expr);
@@ -1954,7 +2003,7 @@ crush_or(struct expr *expr, const struct expr_symbol *symbol)
     }
 
     /* Sort subexpressions by value and mask, to bring together duplicates. */
-    size_t n = list_size(&expr->andor);
+    size_t n = ovs_list_size(&expr->andor);
     struct expr **subs = xmalloc(n * sizeof *subs);
 
     size_t i = 0;
@@ -1966,15 +2015,15 @@ crush_or(struct expr *expr, const struct expr_symbol *symbol)
     qsort(subs, n, sizeof *subs, compare_cmps_cb);
 
     /* Eliminate duplicates. */
-    list_init(&expr->andor);
-    list_push_back(&expr->andor, &subs[0]->node);
+    ovs_list_init(&expr->andor);
+    ovs_list_push_back(&expr->andor, &subs[0]->node);
     for (i = 1; i < n; i++) {
-        struct expr *a = expr_from_node(list_back(&expr->andor));
+        struct expr *a = expr_from_node(ovs_list_back(&expr->andor));
         struct expr *b = subs[i];
         if (compare_cmps_3way(a, b)) {
-            list_push_back(&expr->andor, &b->node);
+            ovs_list_push_back(&expr->andor, &b->node);
         } else {
-            free(b);
+            expr_destroy(b);
         }
     }
     free(subs);
@@ -2011,7 +2060,7 @@ crush_cmps(struct expr *expr, const struct expr_symbol *symbol)
 static struct expr *
 expr_sort(struct expr *expr)
 {
-    size_t n = list_size(&expr->andor);
+    size_t n = ovs_list_size(&expr->andor);
     struct expr_sort *subs = xmalloc(n * sizeof *subs);
     struct expr *sub;
     size_t i;
@@ -2027,7 +2076,7 @@ expr_sort(struct expr *expr)
 
     qsort(subs, n, sizeof *subs, compare_expr_sort);
 
-    list_init(&expr->andor);
+    ovs_list_init(&expr->andor);
     for (int i = 0; i < n; ) {
         if (subs[i].relop) {
             int j;
@@ -2046,7 +2095,7 @@ expr_sort(struct expr *expr)
                     combined = expr_combine(EXPR_T_AND, combined,
                                             subs[k].expr);
                 }
-                ovs_assert(!list_is_short(&combined->andor));
+                ovs_assert(!ovs_list_is_short(&combined->andor));
                 crushed = crush_cmps(combined, subs[i].relop);
             }
             if (crushed->type == EXPR_T_BOOLEAN) {
@@ -2099,15 +2148,15 @@ expr_normalize_and(struct expr *expr)
                                            &b->cmp.value, &b->cmp.mask,
                                            &b->cmp.value, &b->cmp.mask)
                    : !strcmp(a->cmp.string, b->cmp.string)) {
-            list_remove(&a->node);
+            ovs_list_remove(&a->node);
             expr_destroy(a);
         } else {
             expr_destroy(expr);
             return expr_create_boolean(false);
         }
     }
-    if (list_is_short(&expr->andor)) {
-        struct expr *sub = expr_from_node(list_front(&expr->andor));
+    if (ovs_list_is_short(&expr->andor)) {
+        struct expr *sub = expr_from_node(ovs_list_front(&expr->andor));
         free(expr);
         return sub;
     }
@@ -2135,14 +2184,14 @@ expr_normalize_and(struct expr *expr)
 
                         LIST_FOR_EACH (p, node, &term->andor) {
                             struct expr *new = expr_clone(p);
-                            list_push_back(&and->andor, &new->node);
+                            ovs_list_push_back(&and->andor, &new->node);
                         }
                     } else {
                         struct expr *new = expr_clone(term);
-                        list_push_back(&and->andor, &new->node);
+                        ovs_list_push_back(&and->andor, &new->node);
                     }
                 }
-                list_push_back(&or->andor, &and->node);
+                ovs_list_push_back(&or->andor, &and->node);
             }
             expr_destroy(expr);
             return expr_normalize_or(or);
@@ -2158,7 +2207,7 @@ expr_normalize_or(struct expr *expr)
 
     LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
         if (sub->type == EXPR_T_AND) {
-            list_remove(&sub->node);
+            ovs_list_remove(&sub->node);
 
             struct expr *new = expr_normalize_and(sub);
             if (new->type == EXPR_T_BOOLEAN) {
@@ -2174,12 +2223,12 @@ expr_normalize_or(struct expr *expr)
             ovs_assert(sub->type == EXPR_T_CMP);
         }
     }
-    if (list_is_empty(&expr->andor)) {
+    if (ovs_list_is_empty(&expr->andor)) {
         free(expr);
         return expr_create_boolean(false);
     }
-    if (list_is_short(&expr->andor)) {
-        struct expr *sub = expr_from_node(list_pop_front(&expr->andor));
+    if (ovs_list_is_short(&expr->andor)) {
+        struct expr *sub = expr_from_node(ovs_list_pop_front(&expr->andor));
         free(expr);
         return sub;
     }
@@ -2287,17 +2336,18 @@ expr_match_add(struct hmap *matches, struct expr_match *match)
 }
 
 static bool
-constrain_match(const struct expr *expr, const struct simap *ports,
-                struct match *m)
+constrain_match(const struct expr *expr,
+                bool (*lookup_port)(const void *aux, const char *port_name,
+                                    unsigned int *portp),
+                const void *aux, struct match *m)
 {
     ovs_assert(expr->type == EXPR_T_CMP);
     if (expr->cmp.symbol->width) {
         mf_mask_subfield(expr->cmp.symbol->field, &expr->cmp.value,
                          &expr->cmp.mask, m);
     } else {
-        const struct simap_node *node;
-        node = ports ? simap_find(ports, expr->cmp.string) : NULL;
-        if (!node) {
+        unsigned int port;
+        if (!lookup_port(aux, expr->cmp.string, &port)) {
             return false;
         }
 
@@ -2308,7 +2358,7 @@ constrain_match(const struct expr *expr, const struct simap *ports,
 
         union mf_subvalue x;
         memset(&x, 0, sizeof x);
-        x.integer = htonll(node->data);
+        x.integer = htonll(port);
 
         mf_write_subfield(&sf, &x, m);
     }
@@ -2316,7 +2366,10 @@ constrain_match(const struct expr *expr, const struct simap *ports,
 }
 
 static bool
-add_disjunction(const struct expr *or, const struct simap *ports,
+add_disjunction(const struct expr *or,
+                bool (*lookup_port)(const void *aux, const char *port_name,
+                                    unsigned int *portp),
+                const void *aux,
                 struct match *m, uint8_t clause, uint8_t n_clauses,
                 uint32_t conj_id, struct hmap *matches)
 {
@@ -2327,7 +2380,7 @@ add_disjunction(const struct expr *or, const struct simap *ports,
     LIST_FOR_EACH (sub, node, &or->andor) {
         struct expr_match *match = expr_match_new(m, clause, n_clauses,
                                                   conj_id);
-        if (constrain_match(sub, ports, &match->match)) {
+        if (constrain_match(sub, lookup_port, aux, &match->match)) {
             expr_match_add(matches, match);
             n++;
         } else {
@@ -2342,8 +2395,10 @@ add_disjunction(const struct expr *or, const struct simap *ports,
 }
 
 static void
-add_conjunction(const struct expr *and, const struct simap *ports,
-                uint32_t *n_conjsp, struct hmap *matches)
+add_conjunction(const struct expr *and,
+                bool (*lookup_port)(const void *aux, const char *port_name,
+                                    unsigned int *portp),
+                const void *aux, uint32_t *n_conjsp, struct hmap *matches)
 {
     struct match match;
     int n_clauses = 0;
@@ -2355,7 +2410,7 @@ add_conjunction(const struct expr *and, const struct simap *ports,
     LIST_FOR_EACH (sub, node, &and->andor) {
         switch (sub->type) {
         case EXPR_T_CMP:
-            if (!constrain_match(sub, ports, &match)) {
+            if (!constrain_match(sub, lookup_port, aux, &match)) {
                 return;
             }
             break;
@@ -2373,7 +2428,8 @@ add_conjunction(const struct expr *and, const struct simap *ports,
     } else if (n_clauses == 1) {
         LIST_FOR_EACH (sub, node, &and->andor) {
             if (sub->type == EXPR_T_OR) {
-                add_disjunction(sub, ports, &match, 0, 0, 0, matches);
+                add_disjunction(sub, lookup_port, aux, &match, 0, 0, 0,
+                                matches);
             }
         }
     } else {
@@ -2381,7 +2437,7 @@ add_conjunction(const struct expr *and, const struct simap *ports,
         (*n_conjsp)++;
         LIST_FOR_EACH (sub, node, &and->andor) {
             if (sub->type == EXPR_T_OR) {
-                if (!add_disjunction(sub, ports, &match, clause++,
+                if (!add_disjunction(sub, lookup_port, aux, &match, clause++,
                                      n_clauses, *n_conjsp, matches)) {
                     /* This clause can't ever match, so we might as well skip
                      * adding the other clauses--the overall disjunctive flow
@@ -2401,11 +2457,13 @@ add_conjunction(const struct expr *and, const struct simap *ports,
 }
 
 static void
-add_cmp_flow(const struct expr *cmp, const struct simap *ports,
-             struct hmap *matches)
+add_cmp_flow(const struct expr *cmp,
+             bool (*lookup_port)(const void *aux, const char *port_name,
+                                 unsigned int *portp),
+             const void *aux, struct hmap *matches)
 {
     struct expr_match *m = expr_match_new(NULL, 0, 0, 0);
-    if (constrain_match(cmp, ports, &m->match)) {
+    if (constrain_match(cmp, lookup_port, aux, &m->match)) {
         expr_match_add(matches, m);
     } else {
         free(m);
@@ -2433,30 +2491,35 @@ add_cmp_flow(const struct expr *cmp, const struct simap *ports,
  *       caller should remap the conj_id and add the OpenFlow flow with its
  *       desired actions.
  *
- * 'ports' must be a map from strings (presumably names of ports) to integers.
- * Any comparisons against string fields in 'expr' are translated into integers
- * through this map.  A comparison against a string that is not in 'ports' acts
- * like a Boolean "false"; that is, it will always fail to match.  For a simple
- * expression, this means that the overall expression always fails to match,
- * but an expression with a disjunction on the string field might still match
- * on other port names.
+ * 'lookup_port' must be a function to map from a port name to a port number.
+ * When successful, 'lookup_port' stores the port number into '*portp' and
+ * returns true; when there is no port by the given name, it returns false.
+ * 'aux' is passed to 'lookup_port' as auxiliary data.  Any comparisons against
+ * string fields in 'expr' are translated into integers through this function.
+ * A comparison against a string that is not in 'ports' acts like a Boolean
+ * "false"; that is, it will always fail to match.  For a simple expression,
+ * this means that the overall expression always fails to match, but an
+ * expression with a disjunction on the string field might still match on other
+ * port names.
  *
  * (This treatment of string fields might be too simplistic in general, but it
  * seems reasonable for now when string fields are used only for ports.) */
 uint32_t
-expr_to_matches(const struct expr *expr, const struct simap *ports,
-                struct hmap *matches)
+expr_to_matches(const struct expr *expr,
+                bool (*lookup_port)(const void *aux, const char *port_name,
+                                    unsigned int *portp),
+                const void *aux, struct hmap *matches)
 {
     uint32_t n_conjs = 0;
 
     hmap_init(matches);
     switch (expr->type) {
     case EXPR_T_CMP:
-        add_cmp_flow(expr, ports, matches);
+        add_cmp_flow(expr, lookup_port, aux, matches);
         break;
 
     case EXPR_T_AND:
-        add_conjunction(expr, ports, &n_conjs, matches);
+        add_conjunction(expr, lookup_port, aux, &n_conjs, matches);
         break;
 
     case EXPR_T_OR:
@@ -2464,16 +2527,16 @@ expr_to_matches(const struct expr *expr, const struct simap *ports,
             struct expr *sub;
 
             LIST_FOR_EACH (sub, node, &expr->andor) {
-                add_cmp_flow(sub, ports, matches);
+                add_cmp_flow(sub, lookup_port, aux, matches);
             }
         } else {
             struct expr *sub;
 
             LIST_FOR_EACH (sub, node, &expr->andor) {
                 if (sub->type == EXPR_T_AND) {
-                    add_conjunction(sub, ports, &n_conjs, matches);
+                    add_conjunction(sub, lookup_port, aux, &n_conjs, matches);
                 } else {
-                    add_cmp_flow(sub, ports, matches);
+                    add_cmp_flow(sub, lookup_port, aux, matches);
                 }
             }
         }
@@ -2496,10 +2559,9 @@ expr_to_matches(const struct expr *expr, const struct simap *ports,
 void
 expr_matches_destroy(struct hmap *matches)
 {
-    struct expr_match *m, *n;
+    struct expr_match *m;
 
-    HMAP_FOR_EACH_SAFE (m, n, hmap_node, matches) {
-        hmap_remove(matches, &m->hmap_node);
+    HMAP_FOR_EACH_POP (m, hmap_node, matches) {
         free(m->conjunctions);
         free(m);
     }
@@ -2553,7 +2615,7 @@ expr_honors_invariants(const struct expr *expr)
 
     case EXPR_T_AND:
     case EXPR_T_OR:
-        if (list_is_short(&expr->andor)) {
+        if (ovs_list_is_short(&expr->andor)) {
             return false;
         }
         LIST_FOR_EACH (sub, node, &expr->andor) {
@@ -2619,129 +2681,330 @@ expr_is_normalized(const struct expr *expr)
 \f
 /* Action parsing helper. */
 
-static struct expr *
-parse_assignment(struct expr_context *ctx, const struct simap *ports,
-                 struct ofpbuf *ofpacts)
+/* Expands 'f' repeatedly as long as it has an expansion, that is, as long as
+ * it is a subfield or a predicate.  Adds any prerequisites for 'f' to
+ * '*prereqs'.
+ *
+ * If 'rw', verifies that 'f' is a read/write field.
+ *
+ * Returns true if successful, false if an error was encountered (in which case
+ * 'ctx->error' reports the particular error). */
+static bool
+expand_symbol(struct expr_context *ctx, bool rw,
+              struct expr_field *f, struct expr **prereqsp)
 {
-    struct expr *prereqs = NULL;
-
-    struct expr_field f;
-    if (!parse_field(ctx, &f)) {
-        goto exit;
-    }
-    if (!lexer_match(ctx->lexer, LEX_T_EQUALS)) {
-        expr_syntax_error(ctx, "expecting `='.");
-        goto exit;
-    }
-
-    if (f.symbol->expansion && f.symbol->level != EXPR_L_ORDINAL) {
-        expr_error(ctx, "Can't assign to predicate symbol %s.",
-                   f.symbol->name);
-        goto exit;
-    }
-
-    struct expr_constant_set cs;
-    if (!parse_constant_set(ctx, &cs)) {
-        goto exit;
-    }
+    const struct expr_symbol *orig_symbol = f->symbol;
 
-    if (!type_check(ctx, &f, &cs)) {
-        goto exit_destroy_cs;
-    }
-    if (cs.in_curlies) {
-        expr_error(ctx, "Assignments require a single value.");
-        goto exit_destroy_cs;
+    if (f->symbol->expansion && f->symbol->level != EXPR_L_ORDINAL) {
+        expr_error(ctx, "Predicate symbol %s used where lvalue required.",
+                   f->symbol->name);
+        return false;
     }
 
-    const struct expr_symbol *orig_symbol = f.symbol;
-    union expr_constant *c = cs.values;
     for (;;) {
         /* Accumulate prerequisites. */
-        if (f.symbol->prereqs) {
+        if (f->symbol->prereqs) {
             struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting);
             char *error;
             struct expr *e;
-            e = parse_and_annotate(f.symbol->prereqs, ctx->symtab, &nesting,
+            e = parse_and_annotate(f->symbol->prereqs, ctx->symtab, &nesting,
                                    &error);
             if (error) {
                 expr_error(ctx, "%s", error);
                 free(error);
-                goto exit_destroy_cs;
+                return false;
             }
-            prereqs = expr_combine(EXPR_T_AND, prereqs, e);
+            *prereqsp = expr_combine(EXPR_T_AND, *prereqsp, e);
         }
 
         /* If there's no expansion, we're done. */
-        if (!f.symbol->expansion) {
+        if (!f->symbol->expansion) {
             break;
         }
 
         /* Expand. */
         struct expr_field expansion;
         char *error;
-        if (!parse_field_from_string(f.symbol->expansion, ctx->symtab,
+        if (!parse_field_from_string(f->symbol->expansion, ctx->symtab,
                                      &expansion, &error)) {
             expr_error(ctx, "%s", error);
             free(error);
-            goto exit_destroy_cs;
+            return false;
         }
-        f.symbol = expansion.symbol;
-        f.ofs += expansion.ofs;
+        f->symbol = expansion.symbol;
+        f->ofs += expansion.ofs;
     }
 
-    if (!f.symbol->field->writable) {
+    if (rw && !f->symbol->field->writable) {
         expr_error(ctx, "Field %s is not modifiable.", orig_symbol->name);
-        goto exit_destroy_cs;
+        return false;
     }
 
-    struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
-    sf->field = f.symbol->field;
-    if (f.symbol->width) {
-        mf_subvalue_shift(&c->value, f.ofs);
-        if (!c->masked) {
-            memset(&c->mask, 0, sizeof c->mask);
-            bitwise_one(&c->mask, sizeof c->mask, f.ofs, f.n_bits);
-        } else {
-            mf_subvalue_shift(&c->mask, f.ofs);
+    return true;
+}
+
+static void
+mf_subfield_from_expr_field(const struct expr_field *f, struct mf_subfield *sf)
+{
+    sf->field = f->symbol->field;
+    sf->ofs = f->ofs;
+    sf->n_bits = f->n_bits ? f->n_bits : f->symbol->field->n_bits;
+}
+
+static void
+init_stack_action(const struct expr_field *f, struct ofpact_stack *stack)
+{
+    mf_subfield_from_expr_field(f, &stack->subfield);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_assignment(struct lexer *lexer, struct expr_field *dst,
+                 const struct shash *symtab, bool exchange,
+                 bool (*lookup_port)(const void *aux, const char *port_name,
+                                     unsigned int *portp),
+                 const void *aux, struct ofpbuf *ofpacts,
+                 struct expr **prereqsp)
+{
+    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
+    struct expr *prereqs = NULL;
+
+    /* Parse destination and do basic checking. */
+    const struct expr_symbol *orig_dst = dst->symbol;
+    if (!expand_symbol(&ctx, true, dst, &prereqs)) {
+        goto exit;
+    }
+
+    if (exchange || ctx.lexer->token.type == LEX_T_ID) {
+        struct expr_field src;
+        if (!parse_field(&ctx, &src)) {
+            goto exit;
+        }
+        const struct expr_symbol *orig_src = src.symbol;
+        if (!expand_symbol(&ctx, exchange, &src, &prereqs)) {
+            goto exit;
+        }
+
+        if ((dst->symbol->width != 0) != (src.symbol->width != 0)) {
+            if (exchange) {
+                expr_error(&ctx,
+                           "Can't exchange %s field (%s) with %s field (%s).",
+                           orig_dst->width ? "integer" : "string",
+                           orig_dst->name,
+                           orig_src->width ? "integer" : "string",
+                           orig_src->name);
+            } else {
+                expr_error(&ctx,
+                           "Can't assign %s field (%s) to %s field (%s).",
+                           orig_src->width ? "integer" : "string",
+                           orig_src->name,
+                           orig_dst->width ? "integer" : "string",
+                           orig_dst->name);
+            }
+            goto exit;
+        }
+
+        if (dst->n_bits != src.n_bits) {
+            if (exchange) {
+                expr_error(&ctx,
+                           "Can't exchange %d-bit field with %d-bit field.",
+                           dst->n_bits, src.n_bits);
+            } else {
+                expr_error(&ctx,
+                           "Can't assign %d-bit value to %d-bit destination.",
+                           src.n_bits, dst->n_bits);
+            }
+            goto exit;
+        } else if (!dst->n_bits &&
+                   dst->symbol->field->n_bits != src.symbol->field->n_bits) {
+            expr_error(&ctx, "String fields %s and %s are incompatible for "
+                       "%s.", orig_dst->name, orig_src->name,
+                       exchange ? "exchange" : "assignment");
+            goto exit;
         }
 
-        memcpy(&sf->value, &c->value.u8[sizeof c->value - sf->field->n_bytes],
-               sf->field->n_bytes);
-        memcpy(&sf->mask, &c->mask.u8[sizeof c->mask - sf->field->n_bytes],
-               sf->field->n_bytes);
+        if (exchange) {
+            init_stack_action(&src, ofpact_put_STACK_PUSH(ofpacts));
+            init_stack_action(dst, ofpact_put_STACK_PUSH(ofpacts));
+            init_stack_action(&src, ofpact_put_STACK_POP(ofpacts));
+            init_stack_action(dst, ofpact_put_STACK_POP(ofpacts));
+        } else {
+            struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
+            mf_subfield_from_expr_field(&src, &move->src);
+            mf_subfield_from_expr_field(dst, &move->dst);
+        }
     } else {
-        uint32_t port = simap_get(ports, c->string);
-        bitwise_put(port, &sf->value,
-                    sf->field->n_bytes, 0, sf->field->n_bits);
-        bitwise_put(UINT64_MAX, &sf->mask,
-                    sf->field->n_bytes, 0, sf->field->n_bits);
+        struct expr_constant_set cs;
+        if (!parse_constant_set(&ctx, &cs)) {
+            goto exit;
+        }
+
+        if (!type_check(&ctx, dst, &cs)) {
+            goto exit_destroy_cs;
+        }
+        if (cs.in_curlies) {
+            expr_error(&ctx, "Assignments require a single value.");
+            goto exit_destroy_cs;
+        }
+
+        union expr_constant *c = cs.values;
+        struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+        sf->field = dst->symbol->field;
+        if (dst->symbol->width) {
+            mf_subvalue_shift(&c->value, dst->ofs);
+            if (!c->masked) {
+                memset(&c->mask, 0, sizeof c->mask);
+                bitwise_one(&c->mask, sizeof c->mask, dst->ofs, dst->n_bits);
+            } else {
+                mf_subvalue_shift(&c->mask, dst->ofs);
+            }
+
+            memcpy(&sf->value,
+                   &c->value.u8[sizeof c->value - sf->field->n_bytes],
+                   sf->field->n_bytes);
+            memcpy(&sf->mask,
+                   &c->mask.u8[sizeof c->mask - sf->field->n_bytes],
+                   sf->field->n_bytes);
+        } else {
+            uint32_t port;
+            if (!lookup_port(aux, c->string, &port)) {
+                port = 0;
+            }
+            bitwise_put(port, &sf->value,
+                        sf->field->n_bytes, 0, sf->field->n_bits);
+            bitwise_one(&sf->mask, sf->field->n_bytes, 0, sf->field->n_bits);
+
+            /* If the logical input port is being zeroed, clear the OpenFlow
+             * ingress port also, to allow a packet to be sent back to its
+             * origin. */
+            if (!port && sf->field->id == MFF_LOG_INPORT) {
+                sf = ofpact_put_SET_FIELD(ofpacts);
+                sf->field = mf_from_id(MFF_IN_PORT);
+                bitwise_one(&sf->mask,
+                            sf->field->n_bytes, 0, sf->field->n_bits);
+            }
+        }
+
+    exit_destroy_cs:
+        expr_constant_set_destroy(&cs);
     }
 
-exit_destroy_cs:
-    expr_constant_set_destroy(&cs);
 exit:
-    return prereqs;
+    if (ctx.error) {
+        expr_destroy(prereqs);
+        prereqs = NULL;
+    }
+    *prereqsp = prereqs;
+    return ctx.error;
 }
 
 /* A helper for actions_parse(), to parse an OVN assignment action in the form
- * "field = value" into 'ofpacts'.  The parameters and return value match those
- * for actions_parse(). */
-char *
-expr_parse_assignment(struct lexer *lexer, const struct shash *symtab,
-                      const struct simap *ports,
+ * "field = value" or "field = field2" into 'ofpacts'.  The caller must have
+ * already parsed and skipped the left-hand side "field =" and pass in the
+ * field as 'dst'.  Other parameters and return value match those for
+ * actions_parse(). */
+char * OVS_WARN_UNUSED_RESULT
+expr_parse_assignment(struct lexer *lexer, struct expr_field *dst,
+                      const struct shash *symtab,
+                      bool (*lookup_port)(const void *aux,
+                                          const char *port_name,
+                                          unsigned int *portp),
+                      const void *aux,
                       struct ofpbuf *ofpacts, struct expr **prereqsp)
 {
-    struct expr_context ctx;
-    ctx.lexer = lexer;
-    ctx.symtab = symtab;
-    ctx.error = NULL;
-    ctx.not = false;
+    return parse_assignment(lexer, dst, symtab, false, lookup_port, aux,
+                            ofpacts, prereqsp);
+}
 
-    struct expr *prereqs = parse_assignment(&ctx, ports, ofpacts);
-    if (ctx.error) {
+/* A helper for actions_parse(), to parse an OVN exchange action in the form
+ * "field1 <-> field2" into 'ofpacts'.  The caller must have already parsed and
+ * skipped the left-hand side "field1 <->" and pass in 'field1'.  Other
+ * parameters and return value match those for actions_parse(). */
+char * OVS_WARN_UNUSED_RESULT
+expr_parse_exchange(struct lexer *lexer, struct expr_field *field,
+                    const struct shash *symtab,
+                    bool (*lookup_port)(const void *aux,
+                                        const char *port_name,
+                                        unsigned int *portp),
+                    const void *aux,
+                    struct ofpbuf *ofpacts, struct expr **prereqsp)
+{
+    return parse_assignment(lexer, field, symtab, true, lookup_port, aux,
+                            ofpacts, prereqsp);
+}
+
+/* Parses a field or subfield from 'lexer' into 'field', obtaining field names
+ * from 'symtab'.  Returns NULL if successful, otherwise an error message owned
+ * by the caller. */
+char * OVS_WARN_UNUSED_RESULT
+expr_parse_field(struct lexer *lexer, const struct shash *symtab,
+                 struct expr_field *field)
+{
+    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
+    if (!parse_field(&ctx, field)) {
+        memset(field, 0, sizeof *field);
+    }
+    return ctx.error;
+}
+
+/* Takes 'field', which was presumably parsed by expr_parse_field(), and
+ * converts it into mf_subfield 'sf' and a set of prerequisites in '*prereqsp'.
+ *
+ * 'n_bits' specifies the number of bits that the field must have, and 0
+ * indicates a string field; reports an error if 'field' has a different type
+ * or width.  If 'rw' is true, it is an error if 'field' is read-only.  Uses
+ * 'symtab 'for expanding references and 'lexer' for error reporting.
+ *
+ * Returns NULL if successful, otherwise an error message owned by the
+ * caller. */
+char * OVS_WARN_UNUSED_RESULT
+expr_expand_field(struct lexer *lexer, const struct shash *symtab,
+                  const struct expr_field *orig_field, int n_bits, bool rw,
+                  struct mf_subfield *sf, struct expr **prereqsp)
+{
+    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
+    struct expr *prereqs = NULL;
+
+    struct expr_field field = *orig_field;
+    if (!expand_symbol(&ctx, rw, &field, &prereqs)) {
+        goto exit;
+    }
+    ovs_assert(field.n_bits == orig_field->n_bits);
+
+    if (n_bits != field.n_bits) {
+        if (n_bits && field.n_bits) {
+            expr_error(&ctx, "Cannot use %d-bit field %s[%d..%d] "
+                       "where %d-bit field is required.",
+                       orig_field->n_bits, orig_field->symbol->name,
+                       orig_field->ofs,
+                       orig_field->ofs + orig_field->n_bits - 1, n_bits);
+        } else if (n_bits) {
+            expr_error(&ctx, "Cannot use string field %s where numeric "
+                       "field is required.",
+                       orig_field->symbol->name);
+        } else {
+            expr_error(&ctx, "Cannot use numeric field %s where string "
+                       "field is required.",
+                       orig_field->symbol->name);
+        }
+    }
+
+exit:
+    if (!ctx.error) {
+        mf_subfield_from_expr_field(&field, sf);
+        *prereqsp = prereqs;
+    } else {
+        memset(sf, 0, sizeof *sf);
         expr_destroy(prereqs);
-        prereqs = NULL;
+        *prereqsp = NULL;
     }
-    *prereqsp = prereqs;
+    return ctx.error;
+}
+
+char * OVS_WARN_UNUSED_RESULT
+expr_parse_constant_set(struct lexer *lexer, const struct shash *symtab,
+                        struct expr_constant_set *cs)
+{
+    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
+    parse_constant_set(&ctx, cs);
     return ctx.error;
 }