changeset 23723:ab8831d346c1

revamp echo command and move related variables inside tree_evaluator class * pt-stmt.h, pt-stmt.cc (tree_statement::echo_code): New argument, prefix. Pass prefix to tree_print_code constructor instead of VPS4. * basics.txi: Delete documentation for echo_executing_commands. * interpreter.h, interpreter.cc (interpreter::maximum_braindamage): Now a member function. (interpreter::interpreter): Call tree_evaluator::echo directly instead of using Fecho_executing_commands. Don't force input to be echoed if forced_interactive is etst. * input.h, input.cc (Fecho_executing_commands): Delete. (Vecho_executing_commands): Delete variable and all uses. (VPS4): Delete. (set_default_prompts): Don't set global value for PS4 here. (base_reader::do_input_echo): Delete. (gnu_readline): Don't call do_input_echo. * pt-eval.h, pt-eval.cc (Fecho_executing_commands): Delete. (FPS4): Move here from input.cc. (Fecho): Move here from input.cc Rewrite. Handle "echo function on|off" syntax. (echo_state): Move enum declaration to tree_evaluator class from input.h. * ov-usr-fcn.cc (octave_user_script::call, octave_user_function::call): Push echo state before executing commands. Don't print function header or trailer here. (octave_user_function::print_code_function_header, octave_user_function::print_code_function_trailer): New arg, prefix. Pass prefix to tree_print_code constructor instead of VPS4. * pt-eval.h, pt-eval.cc (tree_evaluator::m_PS4, tree_evaluator::m_echo, tree_evaluator::m_echo_state, tree_evaluator::m_echo_file_name, tree_evaluator::m_echo_file_pos, tree_evaluator::m_echo_files): New member variables. (tree_evaluator::echo_state, tree_evaluator::PS4, tree_evaluator::echo, tree_evaluator::push_echo_state, tree_evaluator::set_echo_state, tree_evaluator::set_echo_file_name, tree_evaluator::set_echo_file_pos, tree_evaluator::echo_this_file, tree_evaluator::echo_code): New member functions. (tree_evaluator::visit_break_command, tree_evaluator::visit_continue_command, tree_evaluator::visit_decl_command, tree_evaluator::visit_simple_for_command, tree_evaluator::visit_complex_for_command, tree_evaluator::visit_if_command, tree_evaluator::visit_no_op_command, tree_evaluator::visit_return_command, tree_evaluator::visit_switch_command, tree_evaluator::visit_try_catch_command, tree_evaluator::visit_unwind_protect_command, tree_evaluator::visit_while_command, tree_evaluator::visit_do_until_command): Manage echo_file_pos and echo code here.
author John W. Eaton <jwe@octave.org>
date Fri, 30 Jun 2017 20:59:54 -0400
parents ab9e51f41a29
children 517078d102ff
files doc/interpreter/basics.txi libinterp/corefcn/input.cc libinterp/corefcn/input.h libinterp/corefcn/interpreter.cc libinterp/corefcn/interpreter.h libinterp/corefcn/oct-hist.cc libinterp/octave-value/ov-usr-fcn.cc libinterp/octave-value/ov-usr-fcn.h libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-eval.h libinterp/parse-tree/pt-stmt.cc libinterp/parse-tree/pt-stmt.h
diffstat 12 files changed, 533 insertions(+), 327 deletions(-) [+]
line wrap: on
line diff
--- a/doc/interpreter/basics.txi	Fri Jun 30 01:21:31 2017 -0400
+++ b/doc/interpreter/basics.txi	Fri Jun 30 20:59:54 2017 -0400
@@ -846,8 +846,6 @@
 
 @DOCSTRING(echo)
 
-@DOCSTRING(echo_executing_commands)
-
 @node Errors
 @section How Octave Reports Errors
 @cindex error messages
--- a/libinterp/corefcn/input.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/corefcn/input.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -81,18 +81,6 @@
 // Secondary prompt string.
 static std::string VPS2;
 
-// String printed before echoed input (enabled by --echo-input).
-std::string VPS4 = "+ ";
-
-// Echo commands as they are executed?
-//
-//   1  ==>  echo commands read from script files
-//   2  ==>  echo commands from functions
-//   4  ==>  echo commands read from command line
-//
-// more than one state can be active at once.
-int Vecho_executing_commands = ECHO_OFF;
-
 // The time we last printed a prompt.
 octave::sys::time Vlast_prompt_time = 0.0;
 
@@ -141,45 +129,11 @@
 
   VPS1 = "octave:\\#> ";
   VPS2 = "> ";
-  VPS4 = "+ ";
+  std::string VPS4 = "+ ";
 
   octave_link::set_default_prompts (VPS1, VPS2, VPS4);
 }
 
-namespace octave
-{
-  void
-  base_reader::do_input_echo (const std::string& input_string) const
-  {
-    bool forced_interactive = application::forced_interactive ();
-
-    int do_echo = reading_script_file ()
-      ? (Vecho_executing_commands & ECHO_SCRIPTS)
-      : ((Vecho_executing_commands & ECHO_CMD_LINE) && ! forced_interactive);
-
-    if (do_echo)
-      {
-        if (forced_interactive)
-          {
-            if (pflag > 0)
-              octave_stdout << command_editor::decode_prompt_string (VPS1);
-            else
-              octave_stdout << command_editor::decode_prompt_string (VPS2);
-          }
-        else
-          octave_stdout << command_editor::decode_prompt_string (VPS4);
-
-        if (! input_string.empty ())
-          {
-            octave_stdout << input_string;
-
-            if (input_string[input_string.length () - 1] != '\n')
-              octave_stdout << "\n";
-          }
-      }
-  }
-}
-
 static std::string
 gnu_readline (const std::string& s, bool& eof)
 {
@@ -308,8 +262,6 @@
 
         if (retval[retval.length () - 1] != '\n')
           octave_diary << "\n";
-
-        do_input_echo (retval);
       }
     else
       octave_diary << "\n";
@@ -1025,137 +977,6 @@
   return ovl ();
 }
 
-DEFUN (echo, args, ,
-       doc: /* -*- texinfo -*-
-@deftypefn  {} {} echo
-@deftypefnx {} {} echo on
-@deftypefnx {} {} echo off
-@deftypefnx {} {} echo on all
-@deftypefnx {} {} echo off all
-Control whether commands are displayed as they are executed.
-
-Valid options are:
-
-@table @code
-@item on
-Enable echoing of commands as they are executed in script files.
-
-@item off
-Disable echoing of commands as they are executed in script files.
-
-@item on all
-Enable echoing of commands as they are executed in script files and
-functions.
-
-@item off all
-Disable echoing of commands as they are executed in script files and
-functions.
-@end table
-
-@noindent
-With no arguments, @code{echo} toggles the current echo state.
-
-@seealso{echo_executing_commands}
-@end deftypefn */)
-{
-  string_vector argv = args.make_argv ();
-
-  switch (args.length ())
-    {
-    case 0:
-      {
-        if ((Vecho_executing_commands & ECHO_SCRIPTS)
-            || (Vecho_executing_commands & ECHO_FUNCTIONS))
-          Vecho_executing_commands = ECHO_OFF;
-        else
-          Vecho_executing_commands = ECHO_SCRIPTS;
-      }
-      break;
-
-    case 1:
-      {
-        std::string arg = argv[0];
-
-        if (arg == "on")
-          Vecho_executing_commands = ECHO_SCRIPTS;
-        else if (arg == "off")
-          Vecho_executing_commands = ECHO_OFF;
-        else
-          print_usage ();
-      }
-      break;
-
-    case 2:
-      {
-        std::string arg = argv[0];
-
-        if (arg == "on" && argv[1] == "all")
-          {
-            int tmp = (ECHO_SCRIPTS | ECHO_FUNCTIONS);
-            Vecho_executing_commands = tmp;
-          }
-        else if (arg == "off" && argv[1] == "all")
-          Vecho_executing_commands = ECHO_OFF;
-        else
-          print_usage ();
-      }
-      break;
-
-    default:
-      print_usage ();
-      break;
-    }
-
-  return ovl ();
-}
-
-/*
-%!test
-%! state = echo_executing_commands ();
-%! unwind_protect
-%!   echo ();
-%!   s1 = echo_executing_commands ();
-%!   assert (s1 != state);
-%!   echo ();
-%!   s2 = echo_executing_commands ();
-%!   assert (s2 != s1);
-%! unwind_protect_cleanup
-%!   echo_executing_commands (state);
-%! end_unwind_protect
-
-%!test
-%! state = echo_executing_commands ();
-%! unwind_protect
-%!   echo ("off");
-%!   assert (echo_executing_commands () == 0);
-%!   echo ("on");
-%!   assert (echo_executing_commands () != 0);
-%!   echo ("off");
-%!   assert (echo_executing_commands () == 0);
-%! unwind_protect_cleanup
-%!   echo_executing_commands (state);
-%! end_unwind_protect
-
-%!#test  # FIXME: This passes, but produces a lot of onscreen output
-%! state = echo_executing_commands ();
-%! unwind_protect
-%!   echo ("on", "all");
-%!   assert (echo_executing_commands () != 0);
-%!   echo ("off", "all");
-%!   assert (echo_executing_commands () == 0);
-%! unwind_protect_cleanup
-%!   echo_executing_commands (state);
-%! end_unwind_protect
-
-%!error echo ([])
-%!error echo (0)
-%!error echo ("")
-%!error echo ("Octave")
-%!error echo ("off", "invalid")
-%!error echo ("on", "invalid")
-%!error echo ("on", "all", "all")
-*/
-
 DEFUN (completion_matches, args, nargout,
        doc: /* -*- texinfo -*-
 @deftypefn {} {} completion_matches (@var{hint})
@@ -1437,26 +1258,6 @@
   return SET_INTERNAL_VARIABLE (PS2);
 }
 
-DEFUN (PS4, args, nargout,
-       doc: /* -*- texinfo -*-
-@deftypefn  {} {@var{val} =} PS4 ()
-@deftypefnx {} {@var{old_val} =} PS4 (@var{new_val})
-@deftypefnx {} {} PS4 (@var{new_val}, "local")
-Query or set the character string used to prefix output produced
-when echoing commands is enabled.
-
-The default value is @qcode{"+ "}.
-@xref{Diary and Echo Commands}, for a description of echoing commands.
-
-When called from inside a function with the @qcode{"local"} option, the
-variable is changed locally for the function and any subroutines it calls.
-The original variable value is restored when exiting the function.
-@seealso{echo, echo_executing_commands, PS1, PS2}
-@end deftypefn */)
-{
-  return SET_INTERNAL_VARIABLE (PS4);
-}
-
 DEFUN (completion_append_char, args, nargout,
        doc: /* -*- texinfo -*-
 @deftypefn  {} {@var{val} =} completion_append_char ()
@@ -1475,42 +1276,6 @@
   return SET_INTERNAL_VARIABLE (completion_append_char);
 }
 
-DEFUN (echo_executing_commands, args, nargout,
-       doc: /* -*- texinfo -*-
-@deftypefn  {} {@var{val} =} echo_executing_commands ()
-@deftypefnx {} {@var{old_val} =} echo_executing_commands (@var{new_val})
-@deftypefnx {} {} echo_executing_commands (@var{new_val}, "local")
-Query or set the internal variable that controls the echo state.
-
-It may be the sum of the following values:
-
-@table @asis
-@item 1
-Echo commands read from script files.
-
-@item 2
-Echo commands from functions.
-
-@item 4
-Echo commands read from command line.
-@end table
-
-More than one state can be active at once.  For example, a value of 3 is
-equivalent to the command @kbd{echo on all}.
-
-The value of @code{echo_executing_commands} may be set by the @kbd{echo}
-command or the command line option @option{--echo-commands}.
-
-When called from inside a function with the @qcode{"local"} option, the
-variable is changed locally for the function and any subroutines it calls.
-The original variable value is restored when exiting the function.
-
-@seealso{echo}
-@end deftypefn */)
-{
-  return SET_INTERNAL_VARIABLE (echo_executing_commands);
-}
-
 DEFUN (__request_drawnow__, args, ,
        doc: /* -*- texinfo -*-
 @deftypefn  {} {} __request_drawnow__ ()
--- a/libinterp/corefcn/input.h	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/corefcn/input.h	Fri Jun 30 20:59:54 2017 -0400
@@ -70,20 +70,8 @@
 
 extern void set_default_prompts (void);
 
-extern std::string VPS4;
-
 extern char Vfilemarker;
 
-enum echo_state
-{
-  ECHO_OFF = 0,
-  ECHO_SCRIPTS = 1,
-  ECHO_FUNCTIONS = 2,
-  ECHO_CMD_LINE = 4
-};
-
-extern int Vecho_executing_commands;
-
 extern octave::sys::time Vlast_prompt_time;
 
 namespace octave
@@ -146,8 +134,6 @@
 
     base_lexer *lexer;
 
-    void do_input_echo (const std::string&) const;
-
     static const std::string in_src;
   };
 
--- a/libinterp/corefcn/interpreter.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/corefcn/interpreter.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -173,33 +173,6 @@
   set_liboctave_warning_with_id_handler (warning_with_id);
 }
 
-// What internal options get configured by --traditional.
-
-static void
-maximum_braindamage (void)
-{
-  FPS1 (octave_value (">> "));
-  FPS2 (octave_value (""));
-  FPS4 (octave_value (""));
-  Fbeep_on_error (octave_value (true));
-  Fconfirm_recursive_rmdir (octave_value (false));
-  Fcrash_dumps_octave_core (octave_value (false));
-  Fdisable_diagonal_matrix (octave_value (true));
-  Fdisable_permutation_matrix (octave_value (true));
-  Fdisable_range (octave_value (true));
-  Ffixed_point_format (octave_value (true));
-  Fhistory_timestamp_format_string (octave_value ("%%-- %D %I:%M %p --%%"));
-  Fpage_screen_output (octave_value (false));
-  Fprint_empty_dimensions (octave_value (false));
-  Fsave_default_options (octave_value ("-mat-binary"));
-  Fstruct_levels_to_print (octave_value (0));
-
-  disable_warning ("Octave:abbreviated-property-match");
-  disable_warning ("Octave:data-file-in-path");
-  disable_warning ("Octave:function-name-clash");
-  disable_warning ("Octave:possible-matlab-short-circuit-operator");
-}
-
 DEFUN (quit, args, ,
        doc: /* -*- texinfo -*-
 @deftypefn  {} {} exit
@@ -505,10 +478,8 @@
         // instead of using the interpreter-level functions.
 
         if (options.echo_commands ())
-          {
-            int val = ECHO_SCRIPTS | ECHO_FUNCTIONS | ECHO_CMD_LINE;
-            Fecho_executing_commands (octave_value (val));
-          }
+          m_evaluator.echo
+            (tree_evaluator::ECHO_SCRIPTS | tree_evaluator::ECHO_FUNCTIONS);
 
         std::string docstrings_file = options.docstrings_file ();
         if (! docstrings_file.empty ())
@@ -700,16 +671,8 @@
               return exit_status;
           }
 
-        // Force input to be echoed if not really interactive,
-        // but the user has forced interactive behavior.
-
         if (options.forced_interactive ())
-          {
-            command_editor::blink_matching_paren (false);
-
-            // FIXME: is this the right thing to do?
-            Fecho_executing_commands (octave_value (ECHO_CMD_LINE));
-          }
+          command_editor::blink_matching_paren (false);
       }
 
     // Avoid counting commands executed from startup or script files.
@@ -1310,4 +1273,32 @@
 
     return found;
   }
+
+  // What internal options get configured by --traditional.
+
+  void interpreter::maximum_braindamage (void)
+  {
+    FPS1 (octave_value (">> "));
+    FPS2 (octave_value (""));
+
+    m_evaluator.PS4 ("");
+
+    Fbeep_on_error (octave_value (true));
+    Fconfirm_recursive_rmdir (octave_value (false));
+    Fcrash_dumps_octave_core (octave_value (false));
+    Fdisable_diagonal_matrix (octave_value (true));
+    Fdisable_permutation_matrix (octave_value (true));
+    Fdisable_range (octave_value (true));
+    Ffixed_point_format (octave_value (true));
+    Fhistory_timestamp_format_string (octave_value ("%%-- %D %I:%M %p --%%"));
+    Fpage_screen_output (octave_value (false));
+    Fprint_empty_dimensions (octave_value (false));
+    Fsave_default_options (octave_value ("-mat-binary"));
+    Fstruct_levels_to_print (octave_value (0));
+
+    disable_warning ("Octave:abbreviated-property-match");
+    disable_warning ("Octave:data-file-in-path");
+    disable_warning ("Octave:function-name-clash");
+    disable_warning ("Octave:possible-matlab-short-circuit-operator");
+  }
 }
--- a/libinterp/corefcn/interpreter.h	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/corefcn/interpreter.h	Fri Jun 30 20:59:54 2017 -0400
@@ -242,6 +242,8 @@
     bool m_history_initialized;
 
     bool m_initialized;
+
+    void maximum_braindamage (void);
   };
 }
 
--- a/libinterp/corefcn/oct-hist.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/corefcn/oct-hist.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -492,18 +492,17 @@
 
   file.close ();
 
-  // Turn on command echo, so the output from this will make better
-  // sense.
-
   octave::unwind_protect frame;
 
   frame.add_fcn (unlink_cleanup, name.c_str ());
-  frame.protect_var (Vecho_executing_commands);
   frame.protect_var (input_from_tmp_history_file);
 
-  Vecho_executing_commands = ECHO_CMD_LINE;
   input_from_tmp_history_file = true;
 
+  // FIXME: instead of sourcing a file, we should just iterate through
+  // the list of commands, parsing and executing them one at a time as
+  // if they were entered interactively.
+
   octave::source_file (name);
 }
 
@@ -515,17 +514,17 @@
   if (name.empty ())
     return;
 
-  // Turn on command echo so the output from this will make better sense.
-
   octave::unwind_protect frame;
 
   frame.add_fcn (unlink_cleanup, name.c_str ());
-  frame.protect_var (Vecho_executing_commands);
   frame.protect_var (input_from_tmp_history_file);
 
-  Vecho_executing_commands = ECHO_CMD_LINE;
   input_from_tmp_history_file = true;
 
+  // FIXME: instead of sourcing a file, we should just iterate through
+  // the list of commands, parsing and executing them one at a time as
+  // if they were entered interactively.
+
   octave::source_file (name);
 }
 
@@ -587,6 +586,8 @@
 @seealso{run_history, history}
 @end deftypefn */)
 {
+  // FIXME: should this be limited to the top-level context?
+
   if (args.length () > 2)
     print_usage ();
 
@@ -637,6 +638,8 @@
 @seealso{edit_history, run_history}
 @end deftypefn */)
 {
+  // FIXME: should this be limited to the top-level context?
+
   // Call do_history even if nargout is zero to display history list.
 
   string_vector hlist = do_history (args, nargout);
@@ -696,6 +699,8 @@
 @seealso{edit_history, history}
 @end deftypefn */)
 {
+  // FIXME: should this be limited to the top-level context?
+
   if (args.length () > 2)
     print_usage ();
 
--- a/libinterp/octave-value/ov-usr-fcn.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/octave-value/ov-usr-fcn.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -145,6 +145,10 @@
       profile_data_accumulator::enter<octave_user_script>
         block (profiler, *this);
 
+      if (tw.echo ())
+        tw.push_echo_state (frame, octave::tree_evaluator::ECHO_SCRIPTS,
+                            file_name);
+
       cmd_list->accept (tw);
 
       if (octave::tree_return_command::returning)
@@ -548,11 +552,6 @@
 
   frame.add_method (this, &octave_user_function::restore_warning_states);
 
-  bool echo_commands = (Vecho_executing_commands & ECHO_FUNCTIONS);
-
-  if (echo_commands)
-    print_code_function_header ();
-
   // Evaluate the commands that make up the function.
 
   frame.protect_var (octave::tree_evaluator::statement_context);
@@ -562,6 +561,10 @@
     profile_data_accumulator::enter<octave_user_function>
       block (profiler, *this);
 
+    if (tw.echo ())
+      tw.push_echo_state (frame, octave::tree_evaluator::ECHO_FUNCTIONS,
+                          file_name);
+
     if (is_special_expr ())
       {
         assert (cmd_list->length () == 1);
@@ -581,9 +584,6 @@
       cmd_list->accept (tw);
   }
 
-  if (echo_commands)
-    print_code_function_trailer ();
-
   if (octave::tree_return_command::returning)
     octave::tree_return_command::returning = 0;
 
@@ -701,17 +701,17 @@
 }
 
 void
-octave_user_function::print_code_function_header (void)
+octave_user_function::print_code_function_header (const std::string& prefix)
 {
-  octave::tree_print_code tpc (octave_stdout, VPS4);
+  octave::tree_print_code tpc (octave_stdout, prefix);
 
   tpc.visit_octave_user_function_header (*this);
 }
 
 void
-octave_user_function::print_code_function_trailer (void)
+octave_user_function::print_code_function_trailer (const std::string& prefix)
 {
-  octave::tree_print_code tpc (octave_stdout, VPS4);
+  octave::tree_print_code tpc (octave_stdout, prefix);
 
   tpc.visit_octave_user_function_trailer (*this);
 }
--- a/libinterp/octave-value/ov-usr-fcn.h	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/octave-value/ov-usr-fcn.h	Fri Jun 30 20:59:54 2017 -0400
@@ -473,9 +473,9 @@
 
   void maybe_relocate_end_internal (void);
 
-  void print_code_function_header (void);
+  void print_code_function_header (const std::string& prefix);
 
-  void print_code_function_trailer (void);
+  void print_code_function_trailer (const std::string& prefix);
 
   void bind_automatic_vars (octave::tree_evaluator& tw,
                             const string_vector& arg_names,
--- a/libinterp/parse-tree/pt-eval.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/parse-tree/pt-eval.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -31,6 +31,9 @@
 #include <fstream>
 #include <typeinfo>
 
+#include "cmd-edit.h"
+#include "oct-env.h"
+
 #include "bp-table.h"
 #include "call-stack.h"
 #include "defun.h"
@@ -49,6 +52,7 @@
 #include "pt-tm-const.h"
 #include "symtab.h"
 #include "unwind-prot.h"
+#include "utils.h"
 #include "variables.h"
 
 //FIXME: This should be part of tree_evaluator
@@ -319,6 +323,13 @@
   void
   tree_evaluator::visit_break_command (tree_break_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -394,6 +405,13 @@
   void
   tree_evaluator::visit_continue_command (tree_continue_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -661,6 +679,13 @@
   void
   tree_evaluator::visit_decl_command (tree_decl_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -736,6 +761,14 @@
   void
   tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd)
   {
+    size_t line = cmd.line ();
+
+    if (m_echo_state)
+      {
+        echo_code (line);
+        line++;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -774,6 +807,9 @@
 
         for (octave_idx_type i = 0; i < steps; i++)
           {
+            if (m_echo_state)
+              m_echo_file_pos = line;
+
             octave_value val (rng.elem (i));
 
             ult.assign (octave_value::op_asn_eq, val);
@@ -787,6 +823,9 @@
       }
     else if (rhs.is_scalar_type ())
       {
+        if (m_echo_state)
+          m_echo_file_pos = line;
+
         ult.assign (octave_value::op_asn_eq, rhs);
 
         if (loop_body)
@@ -830,6 +869,9 @@
 
             for (octave_idx_type i = 1; i <= steps; i++)
               {
+                if (m_echo_state)
+                  m_echo_file_pos = line;
+
                 // do_index_op expects one-based indices.
                 idx(iidx) = i;
                 octave_value val = arg.do_index_op (idx);
@@ -857,6 +899,14 @@
   void
   tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd)
   {
+    size_t line = cmd.line ();
+
+    if (m_echo_state)
+      {
+        echo_code (line);
+        line++;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -902,6 +952,9 @@
 
     for (octave_idx_type i = 0; i < nel; i++)
       {
+        if (m_echo_state)
+          m_echo_file_pos = line;
+
         std::string key = keys[i];
 
         const Cell val_lst = tmp_val.contents (key);
@@ -1036,6 +1089,13 @@
   void
   tree_evaluator::visit_if_command (tree_if_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     tree_if_command_list *lst = cmd.cmd_list ();
 
     if (lst)
@@ -1822,6 +1882,13 @@
   void
   tree_evaluator::visit_no_op_command (tree_no_op_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode && cmd.is_end_of_fcn_or_script ())
       do_breakpoint (cmd.is_breakpoint (true), true);
   }
@@ -1971,6 +2038,13 @@
   void
   tree_evaluator::visit_return_command (tree_return_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -2088,14 +2162,6 @@
 
             if (Vtrack_line_num)
               m_call_stack.set_location (stmt.line (), stmt.column ());
-
-            if ((statement_context == script
-                 && ((Vecho_executing_commands & ECHO_SCRIPTS
-                      && m_call_stack.all_scripts ())
-                     || Vecho_executing_commands & ECHO_FUNCTIONS))
-                || (statement_context == function
-                    && Vecho_executing_commands & ECHO_FUNCTIONS))
-              stmt.echo_code ();
           }
 
         try
@@ -2104,6 +2170,13 @@
               cmd->accept (*this);
             else
               {
+                if (m_echo_state)
+                  {
+                    size_t line = stmt.line ();
+                    echo_code (line);
+                    m_echo_file_pos = line + 1;
+                  }
+
                 if (debug_mode)
                   do_breakpoint (expr->is_breakpoint (true));
 
@@ -2216,6 +2289,13 @@
   void
   tree_evaluator::visit_switch_command (tree_switch_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     if (debug_mode)
       do_breakpoint (cmd.is_breakpoint (true));
 
@@ -2249,6 +2329,13 @@
   void
   tree_evaluator::visit_try_catch_command (tree_try_catch_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     bool execution_error = false;
 
     {
@@ -2399,6 +2486,13 @@
   void
   tree_evaluator::visit_unwind_protect_command (tree_unwind_protect_command& cmd)
   {
+    if (m_echo_state)
+      {
+        size_t line = cmd.line ();
+        echo_code (line);
+        m_echo_file_pos = line + 1;
+      }
+
     tree_statement_list *cleanup_code = cmd.cleanup ();
 
     tree_statement_list *unwind_protect_code = cmd.body ();
@@ -2442,6 +2536,14 @@
   void
   tree_evaluator::visit_while_command (tree_while_command& cmd)
   {
+    size_t line = cmd.line ();
+
+    if (m_echo_state)
+      {
+        echo_code (line);
+        line++;
+      }
+
 #if defined (HAVE_LLVM)
     if (tree_jit::execute (cmd))
       return;
@@ -2460,6 +2562,9 @@
 
     for (;;)
       {
+        if (m_echo_state)
+          m_echo_file_pos = line;
+
         if (debug_mode)
           do_breakpoint (cmd.is_breakpoint (true));
 
@@ -2481,6 +2586,14 @@
   void
   tree_evaluator::visit_do_until_command (tree_do_until_command& cmd)
   {
+    size_t line = cmd.line ();
+
+    if (m_echo_state)
+      {
+        echo_code (line);
+        line++;
+      }
+
 #if defined (HAVE_LLVM)
     if (tree_jit::execute (cmd))
       return;
@@ -2501,6 +2614,9 @@
 
     for (;;)
       {
+        if (m_echo_state)
+          m_echo_file_pos = line;
+
         tree_statement_list *loop_body = cmd.body ();
 
         if (loop_body)
@@ -2727,12 +2843,201 @@
                                   "silent_functions");
   }
 
+  void
+  tree_evaluator::push_echo_state (unwind_protect& frame, int type,
+                                   const std::string& file_name)
+  {
+    frame.add_method (*this, &tree_evaluator::set_echo_state,
+                      m_echo_state);
+
+    frame.add_method (*this, &tree_evaluator::set_echo_file_name,
+                      m_echo_file_name);
+
+    frame.add_method (*this, &tree_evaluator::set_echo_file_pos,
+                      m_echo_file_pos);
+
+    m_echo_state = echo_this_file (file_name, type);
+    m_echo_file_name = file_name;
+    m_echo_file_pos = 1;
+  }
+
   octave_value
   tree_evaluator::string_fill_char (const octave_value_list& args, int nargout)
   {
     return set_internal_variable (m_string_fill_char, args, nargout,
                                   "string_fill_char");
   }
+
+  octave_value
+  tree_evaluator::echo (const octave_value_list& args, int)
+  {
+    string_vector argv = args.make_argv ();
+
+    switch (args.length ())
+      {
+      case 0:
+        if ((m_echo & ECHO_SCRIPTS) || (m_echo & ECHO_FUNCTIONS))
+          {
+            m_echo = ECHO_OFF;
+            m_echo_files.clear ();
+          }
+        else
+          m_echo = ECHO_SCRIPTS;
+        break;
+
+      case 1:
+        {
+          std::string arg0 = argv[0];
+
+          if (arg0 == "on")
+            m_echo = ECHO_SCRIPTS;
+          else if (arg0 == "off")
+            m_echo = ECHO_OFF;
+          else
+            {
+              std::string file = fcn_file_in_path (arg0);
+              file = sys::env::make_absolute (file);
+
+              if (file.empty ())
+                error ("echo: no such file %s", arg0.c_str ());
+
+              if (m_echo & ECHO_ALL)
+                {
+                  // Echo is enabled for all functions, so turn it off
+                  // for this one.
+
+                  m_echo_files[file] = false;
+                }
+              else
+                {
+                  // Echo may be enabled for specific functions.
+
+                  auto p = m_echo_files.find (file);
+
+                  if (p == m_echo_files.end ())
+                    {
+                      // Not this one, so enable it.
+
+                      m_echo |= ECHO_FUNCTIONS;
+                      m_echo_files[file] = true;
+                    }
+                  else
+                    {
+                      // This one is already in the list.  Flip the
+                      // status for it.
+
+                      p->second = ! p->second;
+                    }
+                }
+            }
+        }
+        break;
+
+      case 2:
+        {
+          std::string arg0 = argv[0];
+          std::string arg1 = argv[1];
+
+          if (arg1 == "on" || arg1 == "off")
+            std::swap (arg0, arg1);
+
+          if (arg0 == "on")
+            {
+              if (arg1 == "all")
+                {
+                  m_echo = (ECHO_SCRIPTS | ECHO_FUNCTIONS | ECHO_ALL);
+                  m_echo_files.clear ();
+                }
+              else
+                {
+                  std::string file = fcn_file_in_path (arg1);
+                  file = sys::env::make_absolute (file);
+
+                  if (file.empty ())
+                    error ("echo: no such file %s", arg1.c_str ());
+
+                  m_echo |= ECHO_FUNCTIONS;
+                  m_echo_files[file] = true;
+                }
+            }
+          else if (arg0 == "off")
+            {
+              if (arg1 == "all")
+                {
+                  m_echo = ECHO_OFF;
+                  m_echo_files.clear ();
+                }
+              else
+                {
+                  std::string file = fcn_file_in_path (arg1);
+                  file = sys::env::make_absolute (file);
+
+                  if (file.empty ())
+                    error ("echo: no such file %s", arg1.c_str ());
+
+                  m_echo_files[file] = false;
+                }
+            }
+          else
+            print_usage ();
+        }
+        break;
+
+      default:
+        print_usage ();
+        break;
+      }
+
+    return ovl ();
+  }
+
+  octave_value
+  tree_evaluator::PS4 (const octave_value_list& args, int nargout)
+  {
+    return set_internal_variable (m_PS4, args, nargout, "PS4");
+  }
+
+  bool tree_evaluator::echo_this_file (const std::string& file, int type) const
+  {
+    if ((type & m_echo) == ECHO_SCRIPTS)
+      {
+        // Asking about scripts and echo is enabled for them.
+        return true;
+      }
+
+    if ((type & m_echo) == ECHO_FUNCTIONS)
+      {
+        // Asking about functions and echo is enabled for functions.
+        // Now, which ones?
+
+        auto p = m_echo_files.find (file);
+
+        if (m_echo & ECHO_ALL)
+          {
+            // Return true ulness echo was turned off for a specific
+            // file.
+
+            return (p == m_echo_files.end () || p->second);
+          }
+        else
+          {
+            // Return true if echo is specifically enabled for this file.
+
+            return p != m_echo_files.end () && p->second;
+          }
+      }
+
+    return false;
+  }
+
+  void tree_evaluator::echo_code (size_t line)
+  {
+    std::string prefix = command_editor::decode_prompt_string (m_PS4);
+
+    for (size_t i = m_echo_file_pos; i <= line; i++)
+      octave_stdout << prefix << get_file_line (m_echo_file_name, i)
+                    << std::endl;
+  }
 }
 
 DEFMETHOD (max_recursion_depth, interp, args, nargout,
@@ -2850,3 +3155,81 @@
 
 %!error (string_fill_char (1, 2))
 */
+
+DEFMETHOD (PS4, interp, args, nargout,
+           doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{val} =} PS4 ()
+@deftypefnx {} {@var{old_val} =} PS4 (@var{new_val})
+@deftypefnx {} {} PS4 (@var{new_val}, "local")
+Query or set the character string used to prefix output produced
+when echoing commands is enabled.
+
+The default value is @qcode{"+ "}.
+@xref{Diary and Echo Commands}, for a description of echoing commands.
+
+When called from inside a function with the @qcode{"local"} option, the
+variable is changed locally for the function and any subroutines it calls.
+The original variable value is restored when exiting the function.
+@seealso{echo, PS1, PS2}
+@end deftypefn */)
+{
+  octave::tree_evaluator& tw = interp.get_evaluator ();
+
+  return tw.PS4 (args, nargout);
+}
+
+DEFMETHOD (echo, interp, args, nargout,
+           doc: /* -*- texinfo -*-
+@deftypefn  {} {} echo
+@deftypefnx {} {} echo on
+@deftypefnx {} {} echo off
+@deftypefnx {} {} echo on all
+@deftypefnx {} {} echo off all
+@deftypefnx {} {} echo @var{function} on
+@deftypefnx {} {} echo @var{function} off
+Control whether commands are displayed as they are executed.
+
+Valid options are:
+
+@table @code
+@item on
+Enable echoing of commands as they are executed in script files.
+
+@item off
+Disable echoing of commands as they are executed in script files.
+
+@item on all
+Enable echoing of commands as they are executed in script files and
+functions.
+
+@item off all
+Disable echoing of commands as they are executed in script files and
+functions.
+
+@item @var{function} on
+Enable echoing of commands as they are executed in the named function.
+
+@item @var{function} off
+Disable echoing of commands as they are executed in the named function.
+@end table
+
+@noindent
+With no arguments, @code{echo} toggles the current echo state.
+
+@seealso{PS4}
+@end deftypefn */)
+{
+  octave::tree_evaluator& tw = interp.get_evaluator ();
+
+  return tw.echo (args, nargout);
+}
+
+/*
+%!error echo ([])
+%!error echo (0)
+%!error echo ("")
+%!error echo ("Octave")
+%!error echo ("off", "invalid")
+%!error echo ("on", "invalid")
+%!error echo ("on", "all", "all")
+*/
--- a/libinterp/parse-tree/pt-eval.h	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/parse-tree/pt-eval.h	Fri Jun 30 20:59:54 2017 -0400
@@ -26,6 +26,7 @@
 #include "octave-config.h"
 
 #include <list>
+#include <set>
 #include <stack>
 #include <string>
 
@@ -41,6 +42,7 @@
   class tree_expression;
 
   class interpreter;
+  class unwind_protect;
 
   // How to evaluate the code that the parse trees represent.
 
@@ -48,6 +50,14 @@
   {
   public:
 
+    enum echo_state
+    {
+      ECHO_OFF = 0,
+      ECHO_SCRIPTS = 1,
+      ECHO_FUNCTIONS = 2,
+      ECHO_ALL = 4
+    };
+
     template <typename T>
     class value_stack
     {
@@ -107,7 +117,9 @@
       : m_interpreter (interp), m_value_stack (), m_lvalue_list_stack (),
         m_nargout_stack (), m_call_stack (interp),
         m_max_recursion_depth (256), m_silent_functions (false),
-        m_string_fill_char (' ')
+        m_string_fill_char (' '), m_PS4 ("+ "), m_echo (ECHO_OFF),
+        m_echo_state (false), m_echo_file_name (), m_echo_file_pos (1),
+        m_echo_files ()
     { }
 
     // No copying!
@@ -331,6 +343,31 @@
       return val;
     }
 
+    octave_value PS4 (const octave_value_list& args, int nargout);
+
+    std::string PS4 (void) const { return m_PS4; }
+
+    std::string PS4 (const std::string& s)
+    {
+      std::string val = m_PS4;
+      m_PS4 = s;
+      return val;
+    }
+
+    octave_value echo (const octave_value_list& args, int nargout);
+
+    int echo (void) const { return m_echo; }
+
+    int echo (int val)
+    {
+      int old_val = m_echo;
+      m_echo = val;
+      return old_val;
+    }
+
+    void push_echo_state (unwind_protect& frame, int type,
+                          const std::string& file_name);
+
     octave_value
     string_fill_char (const octave_value_list& args, int nargout);
 
@@ -353,6 +390,25 @@
 
     std::list<octave_lvalue> make_lvalue_list (tree_argument_list *);
 
+    // For unwind-protect.
+    void set_echo_state (bool val) { m_echo_state = val; }
+
+    // For unwind-protect.
+    void set_echo_file_name (const std::string& file_name)
+    {
+      m_echo_file_name = file_name;
+    }
+
+    // For unwind-protect.
+    void set_echo_file_pos (const size_t& file_pos)
+    {
+      m_echo_file_pos = file_pos;
+    }
+
+    bool echo_this_file (const std::string& file, int type) const;
+
+    void echo_code (size_t line);
+
     interpreter& m_interpreter;
 
     value_stack<octave_value_list> m_value_stack;
@@ -373,6 +429,26 @@
 
     // The character to fill with when creating string arrays.
     char m_string_fill_char;
+
+    // String printed before echoed commands (enabled by --echo-commands).
+    std::string m_PS4;
+
+    // Echo commands as they are executed?
+    //
+    //   1  ==>  echo commands read from script files
+    //   2  ==>  echo commands from functions
+    //
+    // more than one state can be active at once.
+    int m_echo;
+
+    // Are we currently echoing commands?  This state is set by the
+    // functions that execute fucntions and scripts.
+    bool m_echo_state;
+
+    std::string m_echo_file_name;
+    size_t m_echo_file_pos;
+
+    std::map<std::string, bool> m_echo_files;
   };
 }
 
--- a/libinterp/parse-tree/pt-stmt.cc	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/parse-tree/pt-stmt.cc	Fri Jun 30 20:59:54 2017 -0400
@@ -122,9 +122,9 @@
   }
 
   void
-  tree_statement::echo_code (void)
+  tree_statement::echo_code (const std::string& prefix)
   {
-    tree_print_code tpc (octave_stdout, VPS4);
+    tree_print_code tpc (octave_stdout, prefix);
 
     accept (tpc);
   }
--- a/libinterp/parse-tree/pt-stmt.h	Fri Jun 30 01:21:31 2017 -0400
+++ b/libinterp/parse-tree/pt-stmt.h	Fri Jun 30 20:59:54 2017 -0400
@@ -85,7 +85,7 @@
 
     void set_location (int l, int c);
 
-    void echo_code (void);
+    void echo_code (const std::string& prefix);
 
     tree_command * command (void) { return cmd; }