m_completion_state.expout_completion_name.reset (xstrndup (ptr, length));
 }
 
+/* See parser-defs.h.  */
+
+void
+parser_state::push_c_string (int kind, struct stoken_vector *vec)
+{
+  std::vector<std::string> data (vec->len);
+  for (int i = 0; i < vec->len; ++i)
+    data[i] = std::string (vec->tokens[i].ptr, vec->tokens[i].length);
+
+  push_new<expr::c_string_operation> ((enum c_string_type_values) kind,
+                                     std::move (data));
+}
+
+/* See parser-defs.h.  */
+
+void
+parser_state::push_symbol (const char *name, block_symbol sym)
+{
+  if (sym.symbol != nullptr)
+    {
+      if (symbol_read_needs_frame (sym.symbol))
+       block_tracker->update (sym);
+      push_new<expr::var_value_operation> (sym.symbol, sym.block);
+    }
+  else
+    {
+      struct bound_minimal_symbol msymbol = lookup_bound_minimal_symbol (name);
+      if (msymbol.minsym != NULL)
+       push_new<expr::var_msym_value_operation> (msymbol.minsym,
+                                                 msymbol.objfile);
+      else if (!have_full_symbols () && !have_partial_symbols ())
+       error (_("No symbol table is loaded.  Use the \"file\" command."));
+      else
+       error (_("No symbol \"%s\" in current context."), name);
+    }
+}
+
+/* See parser-defs.h.  */
+
+void
+parser_state::push_dollar (struct stoken str)
+{
+  struct block_symbol sym;
+  struct bound_minimal_symbol msym;
+  struct internalvar *isym = NULL;
+  std::string copy;
+
+  /* Handle the tokens $digits; also $ (short for $0) and $$ (short for $$1)
+     and $$digits (equivalent to $<-digits> if you could type that).  */
+
+  int negate = 0;
+  int i = 1;
+  /* Double dollar means negate the number and add -1 as well.
+     Thus $$ alone means -1.  */
+  if (str.length >= 2 && str.ptr[1] == '$')
+    {
+      negate = 1;
+      i = 2;
+    }
+  if (i == str.length)
+    {
+      /* Just dollars (one or two).  */
+      i = -negate;
+      goto handle_last;
+    }
+  /* Is the rest of the token digits?  */
+  for (; i < str.length; i++)
+    if (!(str.ptr[i] >= '0' && str.ptr[i] <= '9'))
+      break;
+  if (i == str.length)
+    {
+      i = atoi (str.ptr + 1 + negate);
+      if (negate)
+       i = -i;
+      goto handle_last;
+    }
+
+  /* Handle tokens that refer to machine registers:
+     $ followed by a register name.  */
+  i = user_reg_map_name_to_regnum (gdbarch (),
+                                  str.ptr + 1, str.length - 1);
+  if (i >= 0)
+    goto handle_register;
+
+  /* Any names starting with $ are probably debugger internal variables.  */
+
+  copy = copy_name (str);
+  isym = lookup_only_internalvar (copy.c_str () + 1);
+  if (isym)
+    {
+      push_new<expr::internalvar_operation> (isym);
+      return;
+    }
+
+  /* On some systems, such as HP-UX and hppa-linux, certain system routines
+     have names beginning with $ or $$.  Check for those, first.  */
+
+  sym = lookup_symbol (copy.c_str (), NULL, VAR_DOMAIN, NULL);
+  if (sym.symbol)
+    {
+      push_new<expr::var_value_operation> (sym.symbol, sym.block);
+      return;
+    }
+  msym = lookup_bound_minimal_symbol (copy.c_str ());
+  if (msym.minsym)
+    {
+      push_new<expr::var_msym_value_operation> (msym.minsym, msym.objfile);
+      return;
+    }
+
+  /* Any other names are assumed to be debugger internal variables.  */
+
+  push_new<expr::internalvar_operation>
+    (create_internalvar (copy.c_str () + 1));
+  return;
+handle_last:
+  push_new<expr::last_operation> (i);
+  return;
+handle_register:
+  str.length--;
+  str.ptr++;
+  push_new<expr::register_operation> (copy_name (str));
+  block_tracker->update (expression_context_block,
+                        INNERMOST_BLOCK_FOR_REGISTERS);
+  return;
+}
+
 \f
 /* Recognize tokens that start with '$'.  These include:
 
 
 
   void mark_completion_tag (enum type_code tag, const char *ptr, int length);
 
+  /* Push an operation on the stack.  */
+  void push (expr::operation_up &&op)
+  {
+    m_operations.push_back (std::move (op));
+  }
+
+  /* Create a new operation and push it on the stack.  */
+  template<typename T, typename... Arg>
+  void push_new (Arg... args)
+  {
+    m_operations.emplace_back (new T (std::forward<Arg> (args)...));
+  }
+
+  /* Push a new C string operation.  */
+  void push_c_string (int, struct stoken_vector *vec);
+
+  /* Push a symbol reference.  If SYM is nullptr, look for a minimal
+     symbol.  */
+  void push_symbol (const char *name, block_symbol sym);
+
+  /* Push a reference to $mumble.  This may result in a convenience
+     variable, a history reference, or a register.  */
+  void push_dollar (struct stoken str);
+
+  /* Pop an operation from the stack.  */
+  expr::operation_up pop ()
+  {
+    expr::operation_up result = std::move (m_operations.back ());
+    m_operations.pop_back ();
+    return result;
+  }
+
+  /* Pop N elements from the stack and return a vector.  */
+  std::vector<expr::operation_up> pop_vector (int n)
+  {
+    std::vector<expr::operation_up> result (n);
+    for (int i = 1; i <= n; ++i)
+      result[n - i] = pop ();
+    return result;
+  }
+
+  /* A helper that pops an operation, wraps it in some other
+     operation, and pushes it again.  */
+  template<typename T>
+  void wrap ()
+  {
+    using namespace expr;
+    operation_up v = ::expr::make_operation<T> (pop ());
+    push (std::move (v));
+  }
+
+  /* A helper that pops two operations, wraps them in some other
+     operation, and pushes the result.  */
+  template<typename T>
+  void wrap2 ()
+  {
+    expr::operation_up rhs = pop ();
+    expr::operation_up lhs = pop ();
+    push (expr::make_operation<T> (std::move (lhs), std::move (rhs)));
+  }
 
   /* If this is nonzero, this block is used as the lexical context for
      symbol names.  */
      arguments contain other function calls.  */
 
   std::vector<int> m_funcall_chain;
+
+  /* Stack of operations.  */
+  std::vector<expr::operation_up> m_operations;
 };
 
 /* When parsing expressions we track the innermost block that was