changeset 33311:ac3633dd67d1 bytecode-interpreter

maint: merge default to bytecode-interpreter
author John W. Eaton <jwe@octave.org>
date Thu, 04 Apr 2024 01:00:08 -0400
parents 0b4118c91dc9 (current diff) 5b13f73bda6f (diff)
children 3e3a10b70fcd
files libinterp/octave-value/ov-fcn.cc libinterp/octave-value/ov-fcn.h libinterp/octave-value/ov-usr-fcn.cc libinterp/octave-value/ov-usr-fcn.h libinterp/parse-tree/pt-eval.cc
diffstat 54 files changed, 795 insertions(+), 661 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/m-editor/file-editor-tab.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libgui/src/m-editor/file-editor-tab.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -1802,6 +1802,7 @@
     {
       dlg->setWindowModality (Qt::NonModal);
       dlg->show ();
+      dlg->raise ();
     }
 }
 
--- a/libgui/src/m-editor/file-editor.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libgui/src/m-editor/file-editor.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -1194,6 +1194,7 @@
       msgBox->setWindowModality (Qt::NonModal);
       msgBox->setAttribute (Qt::WA_DeleteOnClose);
       msgBox->show ();
+      msgBox->raise ();
 
       return;
     }
@@ -1220,6 +1221,7 @@
       msgBox->setWindowModality (Qt::NonModal);
       msgBox->setAttribute (Qt::WA_DeleteOnClose);
       msgBox->show ();
+      msgBox->raise ();
 
       return;
     }
@@ -1820,6 +1822,7 @@
                           msgBox->setWindowModality (Qt::NonModal);
                           msgBox->setAttribute (Qt::WA_DeleteOnClose);
                           msgBox->show ();
+                          msgBox->raise ();
                         }
                       else
                         {
--- a/libinterp/octave-value/cdef-class.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/octave-value/cdef-class.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -730,12 +730,15 @@
       if (pfcn == nullptr)
         continue;
 
-      const int e = pfcn->ending_line ();
-      if (line <= e && e <= closest_match_end_line && pfcn->is_defined ()
+      octave::filepos end_pos = pfcn->end_pos ();
+
+      int end_line = end_pos.line ();
+
+      if (line <= end_line && end_line <= closest_match_end_line && pfcn->is_defined ()
           && pfcn->is_user_code ())
         {
           closest_match = fcn;
-          closest_match_end_line = e;
+          closest_match_end_line = end_line;
         }
     }
 
--- a/libinterp/octave-value/ov-fcn.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/octave-value/ov-fcn.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -27,18 +27,17 @@
 #  include "config.h"
 #endif
 
+#include "lo-array-errwarn.h"
 #include "unwind-prot.h"
 
 #include "error.h"
+#include "filepos.h"
+#include "interpreter-private.h"
+#include "interpreter.h"
+#include "ov-fcn.h"
 #include "ovl.h"
-#include "ov-fcn.h"
 #include "pt-eval.h"
-
-#include "error.h"
-#include "interpreter-private.h"
 #include "symtab.h"
-#include "interpreter.h"
-#include "lo-array-errwarn.h"
 
 #include "pt-bytecode-walk.h"
 
@@ -54,6 +53,18 @@
   panic_impossible ();
 }
 
+octave::filepos
+octave_function::beg_pos () const
+{
+  panic_impossible ();
+}
+
+octave::filepos
+octave_function::end_pos () const
+{
+  panic_impossible ();
+}
+
 octave_value_list
 octave_function::call (octave::tree_evaluator& tw, int nargout,
                        const octave_value_list& args)
--- a/libinterp/octave-value/ov-fcn.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/octave-value/ov-fcn.h	Thu Apr 04 01:00:08 2024 -0400
@@ -40,6 +40,7 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
+class filepos;
 class stack_frame;
 class tree_evaluator;
 class tree_walker;
@@ -161,6 +162,9 @@
 
   virtual std::string src_file_name () const { return ""; }
 
+  virtual octave::filepos beg_pos () const;
+  virtual octave::filepos end_pos () const;
+
   // The name to show in the profiler (also used as map-key).
   virtual std::string profiler_name () const { return name (); }
 
--- a/libinterp/octave-value/ov-usr-fcn.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/octave-value/ov-usr-fcn.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -27,6 +27,8 @@
 #  include "config.h"
 #endif
 
+#include <iostream>
+
 #include <sstream>
 
 #include "file-info.h"
@@ -40,6 +42,7 @@
 #include "defun.h"
 #include "error.h"
 #include "errwarn.h"
+#include "filepos.h"
 #include "input.h"
 #include "ovl.h"
 #include "ov-usr-fcn.h"
@@ -47,6 +50,7 @@
 #include "pager.h"
 #include "pt-cmd.h"
 #include "pt-eval.h"
+#include "pt-id.h"
 #include "pt-jump.h"
 #include "pt-misc.h"
 #include "pt-pr-code.h"
@@ -89,6 +93,16 @@
   delete m_file_info;
 }
 
+octave::filepos octave_user_code::beg_pos () const
+{
+  return m_cmd_list->beg_pos ();
+}
+
+octave::filepos octave_user_code::end_pos () const
+{
+  return m_cmd_list->end_pos ();
+}
+
 void
 octave_user_code::get_file_info ()
 {
@@ -259,17 +273,8 @@
 // Ugh.  This really needs to be simplified (code/data?
 // extrinsic/intrinsic state?).
 
-octave_user_function::octave_user_function
-(const octave::symbol_scope& scope, octave::tree_parameter_list *pl,
- octave::tree_parameter_list *rl, octave::tree_statement_list *cl)
-  : octave_user_code ("", "", scope, cl, ""),
-    m_param_list (pl), m_ret_list (rl),
-    m_location_line (0), m_location_column (0),
-    m_system_fcn_file (false),
-    m_num_named_args (m_param_list ? m_param_list->size () : 0),
-    m_subfunction (false), m_inline_function (false),
-    m_anonymous_function (false), m_nested_function (false),
-    m_class_constructor (none), m_class_method (none)
+octave_user_function::octave_user_function (const octave::symbol_scope& scope, octave::tree_identifier *id, octave::tree_parameter_list *pl, octave::tree_parameter_list *rl, octave::tree_statement_list *cl)
+  : octave_user_code ("", "", scope, cl, ""), m_id (id), m_param_list (pl), m_ret_list (rl), m_num_named_args (m_param_list ? m_param_list->size () : 0)
 {
   if (m_cmd_list)
     m_cmd_list->mark_as_function_body ();
@@ -277,6 +282,7 @@
 
 octave_user_function::~octave_user_function ()
 {
+  delete m_id;
   delete m_param_list;
   delete m_ret_list;
 }
@@ -349,13 +355,14 @@
 // relocate the no_op that was generated for the end of file condition
 // to appear on the next line after the last statement in the file, or
 // the next line after the function keyword if there are no statements.
-// More precisely, the new location should probably be on the next line
-// after the end of the parameter list, but we aren't tracking that
-// information (yet).
 
 void
 octave_user_function::maybe_relocate_end_internal ()
 {
+  // This shouldn't happen, but check and return early anyway.
+  if (m_anonymous_function)
+    return;
+
   if (m_cmd_list && ! m_cmd_list->empty ())
     {
       octave::tree_statement *last_stmt = m_cmd_list->back ();
@@ -363,28 +370,37 @@
       if (last_stmt && last_stmt->is_end_of_fcn_or_script ()
           && last_stmt->is_end_of_file ())
         {
-          octave::tree_statement_list::reverse_iterator
-          next_to_last_elt = m_cmd_list->rbegin ();
-
+          octave::tree_statement_list::reverse_iterator next_to_last_elt = m_cmd_list->rbegin ();
           next_to_last_elt++;
 
-          int new_eof_line;
-          int new_eof_col;
+          octave::filepos new_eof_pos;
 
           if (next_to_last_elt == m_cmd_list->rend ())
             {
-              new_eof_line = beginning_line ();
-              new_eof_col = beginning_column ();
+              // Empty function body, just the end statement.  Set the
+              // new beginning of that statement to the end of the
+              // argument list (if any) or the end of the function name.
+
+              // M_ID is only nullptr if this is an anonymous function
+              // and we shouldn't be updating the end position for that.
+              // So if there is no name and no parameter list, just
+              // return early.
+
+              if (m_param_list)
+                new_eof_pos = m_param_list->end_pos ();
+              else if (m_id)
+                new_eof_pos = m_id->end_pos ();
+              else
+                return;
             }
           else
             {
               octave::tree_statement *next_to_last_stmt = *next_to_last_elt;
 
-              new_eof_line = next_to_last_stmt->line ();
-              new_eof_col = next_to_last_stmt->column ();
+              new_eof_pos = next_to_last_stmt->end_pos ();
             }
 
-          last_stmt->set_location (new_eof_line + 1, new_eof_col);
+          last_stmt->update_end_pos (new_eof_pos);
         }
     }
 }
@@ -419,9 +435,14 @@
 {
   std::ostringstream result;
 
+  octave::filepos bp = beg_pos ();
+
+  int bp_line = bp.line ();
+  int bp_column = bp.column ();
+
   if (is_anonymous_function ())
     result << "anonymous@" << fcn_file_name ()
-           << ':' << m_location_line << ':' << m_location_column;
+           << ':' << bp_line << ':' << bp_column;
   else if (is_subfunction ())
     result << parent_fcn_name () << '>' << name ();
   else if (is_class_method ())
@@ -430,7 +451,7 @@
     result << '@' << name ();
   else if (is_inline_function ())
     result << "inline@" << fcn_file_name ()
-           << ':' << m_location_line << ':' << m_location_column;
+           << ':' << bp_line << ':' << bp_column;
   else
     result << name ();
 
@@ -691,20 +712,23 @@
 octave_value
 octave_user_function::dump () const
 {
+  octave::filepos bp = beg_pos ();
+  octave::filepos ep = end_pos ();
+
   std::map<std::string, octave_value> m
-  = {{ "user_code", octave_user_code::dump () },
-    { "line", m_location_line },
-    { "col", m_location_column },
-    { "end_line", m_end_location_line },
-    { "end_col", m_end_location_column },
-    { "system_fcn_file", m_system_fcn_file },
-    { "num_named_args", m_num_named_args },
-    { "subfunction", m_subfunction },
-    { "inline_function", m_inline_function },
-    { "anonymous_function", m_anonymous_function },
-    { "nested_function", m_nested_function },
-    { "ctor_type", ctor_type_str () },
-    { "class_method", m_class_method }
+    = {{ "user_code", octave_user_code::dump () },
+       { "line", bp.line () },
+       { "col", bp.column () },
+       { "end_line", ep.line () },
+       { "end_col", ep.column () },
+       { "system_fcn_file", m_system_fcn_file },
+       { "num_named_args", m_num_named_args },
+       { "subfunction", m_subfunction },
+       { "inline_function", m_inline_function },
+       { "anonymous_function", m_anonymous_function },
+       { "nested_function", m_nested_function },
+       { "ctor_type", ctor_type_str () },
+       { "class_method", m_class_method }
   };
 
   return octave_value (m);
--- a/libinterp/octave-value/ov-usr-fcn.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/octave-value/ov-usr-fcn.h	Thu Apr 04 01:00:08 2024 -0400
@@ -31,6 +31,7 @@
 #include <string>
 
 #include "comment-list.h"
+#include "filepos.h"
 #include "ovl.h"
 #include "ov-fcn.h"
 #include "ov-typeinfo.h"
@@ -47,8 +48,10 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
+class filepos;
 class file_info;
 class stack_frame;
+class tree_identifier;
 class tree_parameter_list;
 class tree_statement_list;
 class tree_evaluator;
@@ -82,6 +85,9 @@
 
   bool is_user_code () const { return true; }
 
+  octave::filepos beg_pos () const;
+  octave::filepos end_pos () const;
+
   std::string get_code_line (std::size_t line);
 
   std::deque<std::string> get_code_lines (std::size_t line,
@@ -227,10 +233,8 @@
 {
 public:
 
-  octave_user_function (const octave::symbol_scope& scope = octave::symbol_scope::anonymous (),
-                        octave::tree_parameter_list *pl = nullptr,
-                        octave::tree_parameter_list *rl = nullptr,
-                        octave::tree_statement_list *cl = nullptr);
+  octave_user_function (const octave::symbol_scope& scope = octave::symbol_scope::anonymous (), octave::tree_identifier *id = nullptr,
+                        octave::tree_parameter_list *pl = nullptr, octave::tree_parameter_list *rl = nullptr, octave::tree_statement_list *cl = nullptr);
 
   OCTAVE_DISABLE_COPY_MOVE (octave_user_function)
 
@@ -254,23 +258,8 @@
 
   void attach_trailing_comments (const octave::comment_list& lst);
 
-  void stash_fcn_location (int line, int col)
-  {
-    m_location_line = line;
-    m_location_column = col;
-  }
-
-  int beginning_line () const { return m_location_line; }
-  int beginning_column () const { return m_location_column; }
-
-  void stash_fcn_end_location (int line, int col)
-  {
-    m_end_location_line = line;
-    m_end_location_column = col;
-  }
-
-  int ending_line () const { return m_end_location_line; }
-  int ending_column () const { return m_end_location_column; }
+  octave::filepos beg_pos () const { return m_fcn_tok.beg_pos(); }
+  // The end_pos function is defined in the octave_user_code class.
 
   void maybe_relocate_end ();
 
@@ -437,6 +426,9 @@
   std::string ctor_type_str () const;
   std::string method_type_str () const;
 
+  // Name of this function.
+  octave::tree_identifier *m_id;
+
   // List of arguments for this function.  These are local variables.
   octave::tree_parameter_list *m_param_list;
 
@@ -444,42 +436,38 @@
   // this function.
   octave::tree_parameter_list *m_ret_list;
 
-  // FIXME: Should we also be caching the final token (END or EOF) as
-  // m_end_tok?
+  // We don't keep track of an end token separately because functions
+  // may still be defined without an explicit END.  If there is an
+  // explicit END, the final statement will contain it.
+
   octave::token m_fcn_tok;
   octave::token m_eq_tok;
 
-  // Location where this function was defined.
-  int m_location_line;
-  int m_location_column;
-  int m_end_location_line;
-  int m_end_location_column;
-
   // True if this function came from a file that is considered to be a
   // system function.  This affects whether we check the time stamp
   // on the file to see if it has changed.
-  bool m_system_fcn_file;
+  bool m_system_fcn_file {false};
 
   // The number of arguments that have names.
   int m_num_named_args;
 
   // TRUE means this is a m_subfunction of a primary function.
-  bool m_subfunction;
+  bool m_subfunction {false};
 
   // TRUE means this is an inline function.
-  bool m_inline_function;
+  bool m_inline_function {false};
 
   // TRUE means this is an anonymous function.
-  bool m_anonymous_function;
+  bool m_anonymous_function {false};
 
   // TRUE means this is a nested function.
-  bool m_nested_function;
+  bool m_nested_function {false};
 
   // Enum describing whether this function is the constructor for class object.
-  class_method_type m_class_constructor;
+  class_method_type m_class_constructor {none};
 
   // Enum describing whether this function is a method for a class.
-  class_method_type m_class_method;
+  class_method_type m_class_method {none};
 
   void maybe_relocate_end_internal ();
 
--- a/libinterp/parse-tree/bp-table.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/bp-table.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -665,12 +665,10 @@
 
 // Return the sub/nested/main function of MAIN_FCN that contains
 // line number LINENO of the source file.
-// If END_LINE != 0, *END_LINE is set to last line of the returned function.
+// If FOUND_ENDING_LINE != 0, *FOUND_ENDING_LINE is set to last line of the returned function.
 
 static octave_user_code *
-find_fcn_by_line (octave_user_code *main_fcn,
-                  int lineno,
-                  int *end_line = nullptr)
+find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *found_end_line = nullptr)
 {
   octave_user_code *retval = nullptr;
   octave_user_code *next_fcn = nullptr;  // 1st function starting after lineno
@@ -686,21 +684,26 @@
           auto *dbg_subfcn = str_val_p.second.user_function_value ();
 
           // Check if lineno is within dbg_subfcn.
-          // FIXME: we could break when beginning_line() > lineno,
+          // FIXME: we could break when the beginning line > lineno,
           // but that makes the code "fragile"
           // if the order of walking subfcns changes,
           // for a minor speed improvement in non-critical code.
-          if (dbg_subfcn->ending_line () < earliest_end
-              && dbg_subfcn->ending_line () >= lineno
-              && dbg_subfcn->beginning_line () <= lineno)
+
+          filepos beg_pos = dbg_subfcn->beg_pos ();
+          filepos end_pos = dbg_subfcn->end_pos ();
+
+          int beginning_line = beg_pos.line ();
+          int ending_line = end_pos.line ();
+
+          if (ending_line < earliest_end && ending_line >= lineno && beginning_line <= lineno)
             {
-              earliest_end = dbg_subfcn->ending_line ();
+              earliest_end = ending_line;
               retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
             }
 
           // Find the first fcn starting after lineno.
           // This is used if line is not inside any function.
-          if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn)
+          if (beginning_line >= lineno && ! next_fcn)
             next_fcn = dbg_subfcn;
         }
     }
@@ -709,8 +712,11 @@
   // or in the main function, which we check now.
   if (main_fcn->is_user_function ())
     {
-      int e = dynamic_cast<octave_user_function *> (main_fcn)->ending_line ();
-      if (e >= lineno && e < earliest_end)
+      filepos end_pos = dynamic_cast<octave_user_function *> (main_fcn)->end_pos ();
+
+      int ending_line = end_pos.line ();
+
+      if (ending_line >= lineno && ending_line < earliest_end)
         retval = main_fcn;
 
       if (! retval)
@@ -722,8 +728,8 @@
         retval = main_fcn;
     }
 
-  if (end_line && earliest_end < *end_line)
-    *end_line = earliest_end;
+  if (found_end_line && earliest_end < *found_end_line)
+    *found_end_line = earliest_end;
 
   return retval;
 }
--- a/libinterp/parse-tree/oct-parse.yy	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/oct-parse.yy	Thu Apr 04 01:00:08 2024 -0400
@@ -210,6 +210,8 @@
 %token <tok> HERMITIAN TRANSPOSE
 %token <tok> PLUS_PLUS MINUS_MINUS POW EPOW
 %token <tok> NUMBER
+// The result of a constant folding operation.
+%token <tok> CONSTANT
 %token <tok> STRUCT_ELT
 %token <tok> NAME
 %token <tok> END
@@ -634,7 +636,7 @@
 
 anon_fcn_handle : '@' param_list anon_fcn_begin expression
                   {
-                    $$ = parser.make_anon_fcn_handle ($2, $4, $1->beg_pos ());
+                    $$ = parser.make_anon_fcn_handle ($1, $2, $4);
                     if (! $$)
                       {
                         // make_anon_fcn_handle deleted $2 and $4.
@@ -1470,8 +1472,7 @@
                       }
                     else
                       {
-                        octave::tree_statement *end_of_script
-                          = parser.make_end ("endscript", true, $4, $4->beg_pos (), $4->end_pos ());
+                        octave::tree_statement *end_of_script = parser.make_end ("endscript", true, $4);
 
                         parser.make_script ($3, end_of_script);
                       }
@@ -1543,7 +1544,7 @@
                     parser.endfunction_found (true);
 
                     if (parser.end_token_ok ($1, octave::token::function_end))
-                      $$ = parser.make_end ("endfunction", false, $1, $1->beg_pos (), $1->end_pos ());
+                      $$ = parser.make_end ("endfunction", false, $1);
                     else
                       {
                         parser.end_token_error ($1, octave::token::function_end);
@@ -1580,7 +1581,7 @@
                         YYABORT;
                       }
 
-                    $$ = parser.make_end ("endfunction", true, $1, $1->beg_pos (), $1->end_pos ());
+                    $$ = parser.make_end ("endfunction", true, $1);
                   }
                 ;
 
@@ -2718,9 +2719,6 @@
   tree_constant *
   base_parser::make_constant (token *tok)
   {
-    int l = tok->line ();
-    int c = tok->column ();
-
     int op = tok->token_id ();
 
     tree_constant *retval = nullptr;
@@ -2728,17 +2726,11 @@
     switch (op)
       {
       case ':':
-        {
-          octave_value tmp (octave_value::magic_colon_t);
-          retval = new tree_constant (tmp);
-        }
+        retval = new tree_constant (octave_value (octave_value::magic_colon_t), *tok);
         break;
 
       case NUMBER:
-        {
-          retval = new tree_constant (tok->number (), l, c);
-          retval->stash_original_text (tok->text_rep ());
-        }
+        retval = new tree_constant (tok->number (), tok->text_rep (), *tok);
         break;
 
       case DQ_STRING:
@@ -2757,14 +2749,13 @@
                 tmp = octave_null_sq_str::instance;
             }
 
-          retval = new tree_constant (tmp, l, c);
-
           if (op == DQ_STRING)
             txt = undo_string_escapes (txt);
 
-          // FIXME: maybe this should also be handled by
+          // FIXME: maybe the addition of delims should be handled by
           // tok->text_rep () for character strings?
-          retval->stash_original_text (delim + txt + delim);
+
+          retval = new tree_constant (tmp, delim + txt + delim, *tok);
         }
         break;
 
@@ -2787,10 +2778,7 @@
   tree_fcn_handle *
   base_parser::make_fcn_handle (token *tok)
   {
-    int l = tok->line ();
-    int c = tok->column ();
-
-    tree_fcn_handle *retval = new tree_fcn_handle (tok->text (), l, c);
+    tree_fcn_handle *retval = new tree_fcn_handle (*tok);
 
     return retval;
   }
@@ -2798,9 +2786,7 @@
   // Make an anonymous function handle.
 
   tree_anon_fcn_handle *
-  base_parser::make_anon_fcn_handle (tree_parameter_list *param_list,
-                                     tree_expression *expr,
-                                     const filepos& at_pos)
+  base_parser::make_anon_fcn_handle (token *at_tok, tree_parameter_list *param_list, tree_expression *expr)
   {
     // FIXME: We need to examine EXPR and issue an error if any
     // sub-expression contains an assignment, compound assignment,
@@ -2813,8 +2799,7 @@
         delete param_list;
         delete expr;
 
-        bison_error (validator.message (), validator.line (),
-                     validator.column ());
+        bison_error (validator.message (), validator.line (), validator.column ());
 
         return nullptr;
       }
@@ -2828,12 +2813,8 @@
 
     fcn_scope.mark_static ();
 
-    int at_line = at_pos.line ();
-    int at_column = at_pos.column ();
-
     tree_anon_fcn_handle *retval
-      = new tree_anon_fcn_handle (param_list, expr, fcn_scope,
-                                  parent_scope, at_line, at_column);
+      = new tree_anon_fcn_handle (*at_tok, param_list, expr, fcn_scope, parent_scope);
 
     std::ostringstream buf;
 
@@ -2848,7 +2829,9 @@
       buf << ": *terminal input*";
     else if (m_lexer.input_from_eval_string ())
       buf << ": *eval string*";
-    buf << ": line: " << at_line << " column: " << at_column;
+
+    filepos at_pos = at_tok->beg_pos ();
+    buf << ": line: " << at_pos.line () << " column: " << at_pos.column ();
 
     std::string scope_name = buf.str ();
 
@@ -2883,13 +2866,10 @@
         return retval;
       }
 
-    int l = base->line ();
-    int c = base->column ();
-
     token tmp_colon_2_tok = colon_2_tok ? *colon_2_tok : token ();
 
     tree_colon_expression *expr
-      = new tree_colon_expression (base, *colon_1_tok, incr, tmp_colon_2_tok, limit, l, c);
+      = new tree_colon_expression (base, *colon_1_tok, incr, tmp_colon_2_tok, limit);
 
     retval = expr;
 
@@ -2922,16 +2902,17 @@
 
             if (msg.empty ())
               {
-                tree_constant *tc_retval
-                  = new tree_constant (tmp, expr->line (), expr->column ());
-
                 std::ostringstream buf;
 
                 tree_print_code tpc (buf);
 
                 expr->accept (tpc);
 
-                tc_retval->stash_original_text (buf.str ());
+                std::string orig_text = buf.str ();
+
+                token tok (CONSTANT, tmp, orig_text, expr->beg_pos (), expr->end_pos ());
+
+                tree_constant *tc_retval = new tree_constant (tmp, orig_text, tok);
 
                 delete expr;
 
@@ -3033,10 +3014,7 @@
         break;
       }
 
-    int l = op_tok->line ();
-    int c = op_tok->column ();
-
-    return maybe_compound_binary_expression (op1, op2, l, c, t);
+    return maybe_compound_binary_expression (op1, *op_tok, op2, t);
   }
 
   void
@@ -3044,8 +3022,9 @@
   {
     if (expr->is_binary_expression ())
       {
-        tree_binary_expression *binexp
-          = dynamic_cast<tree_binary_expression *> (expr);
+        tree_binary_expression *binexp = dynamic_cast<tree_binary_expression *> (expr);
+
+        token op_tok = binexp->operator_token ();
 
         tree_expression *lhs = binexp->lhs ();
         tree_expression *rhs = binexp->rhs ();
@@ -3063,13 +3042,9 @@
           {
             binexp->preserve_operands ();
 
-            int line = expr->line ();
-            int column = expr->column ();
-
             delete expr;
 
-            expr = new tree_braindead_shortcircuit_binary_expression
-              (lhs, rhs, line, column, op_type);
+            expr = new tree_braindead_shortcircuit_binary_expression (lhs, op_tok, rhs, op_type);
           }
       }
   }
@@ -3096,10 +3071,7 @@
         break;
       }
 
-    int l = op_tok->line ();
-    int c = op_tok->column ();
-
-    return new tree_boolean_expression (op1, op2, l, c, t);
+    return new tree_boolean_expression (op1, *op_tok, op2, t);
   }
 
   // Build a prefix expression.
@@ -3137,10 +3109,7 @@
         break;
       }
 
-    int l = op_tok->line ();
-    int c = op_tok->column ();
-
-    return new tree_prefix_expression (op1, l, c, t);
+    return new tree_prefix_expression (*op_tok, op1, t);
   }
 
   // Build a postfix expression.
@@ -3173,10 +3142,7 @@
         break;
       }
 
-    int l = op_tok->line ();
-    int c = op_tok->column ();
-
-    return new tree_postfix_expression (op1, l, c, t);
+    return new tree_postfix_expression (op1, *op_tok, t);
   }
 
   // Build an unwind-protect command.
@@ -3188,10 +3154,7 @@
 
     if (end_token_ok (end_tok, token::unwind_protect_end))
       {
-        int l = unwind_tok->line ();
-        int c = unwind_tok->column ();
-
-        retval = new tree_unwind_protect_command (*unwind_tok, body, *cleanup_tok, cleanup_stmts, *end_tok, l, c);
+        retval = new tree_unwind_protect_command (*unwind_tok, body, *cleanup_tok, cleanup_stmts, *end_tok);
       }
     else
       {
@@ -3213,9 +3176,6 @@
 
     if (end_token_ok (end_tok, token::try_catch_end))
       {
-        int l = try_tok->line ();
-        int c = try_tok->column ();
-
         tree_identifier *id = nullptr;
 
         // Look for exception ID.  Could this be done in the grammar or
@@ -3243,7 +3203,7 @@
 
         token tmp_catch_tok = catch_tok ? *catch_tok : token ();
 
-        retval = new tree_try_catch_command (*try_tok, body, tmp_catch_tok, id, cleanup_stmts, *end_tok, l, c);
+        retval = new tree_try_catch_command (*try_tok, body, tmp_catch_tok, id, cleanup_stmts, *end_tok);
       }
     else
       {
@@ -3269,10 +3229,7 @@
       {
         m_lexer.m_looping--;
 
-        int l = while_tok->line ();
-        int c = while_tok->column ();
-
-        retval = new tree_while_command (*while_tok, expr, body, *end_tok, l, c);
+        retval = new tree_while_command (*while_tok, expr, body, *end_tok);
       }
     else
       {
@@ -3294,10 +3251,7 @@
 
     m_lexer.m_looping--;
 
-    int l = until_tok->line ();
-    int c = until_tok->column ();
-
-    return new tree_do_until_command (*do_tok, body, *until_tok, expr, l, c);
+    return new tree_do_until_command (*do_tok, body, *until_tok, expr);
   }
 
   // Build a for command.
@@ -3319,16 +3273,13 @@
 
         m_lexer.m_looping--;
 
-        int l = for_tok->line ();
-        int c = for_tok->column ();
-
         if (lhs->size () == 1)
           {
             tree_expression *tmp = lhs->remove_front ();
 
             m_lexer.mark_as_variable (tmp->name ());
 
-            retval = new tree_simple_for_command (parfor, *for_tok, tmp_open_paren, tmp, *eq_tok, expr, tmp_sep_tok, maxproc, tmp_close_paren, body, *end_tok, l, c);
+            retval = new tree_simple_for_command (parfor, *for_tok, tmp_open_paren, tmp, *eq_tok, expr, tmp_sep_tok, maxproc, tmp_close_paren, body, *end_tok);
 
             delete lhs;
           }
@@ -3345,7 +3296,7 @@
           {
             m_lexer.mark_as_variables (lhs->variable_names ());
 
-            retval = new tree_complex_for_command (*for_tok, lhs, *eq_tok, expr, body, *end_tok, l, c);
+            retval = new tree_complex_for_command (*for_tok, lhs, *eq_tok, expr, body, *end_tok);
           }
       }
     else
@@ -3366,16 +3317,13 @@
   tree_command *
   base_parser::make_break_command (token *break_tok)
   {
-    int l = break_tok->line ();
-    int c = break_tok->column ();
-
     if (! m_lexer.m_looping)
       {
         bison_error ("break must appear within a loop");
         return nullptr;
       }
     else
-      return new tree_break_command (l, c);
+      return new tree_break_command (*break_tok);
   }
 
   // Build a continue command.
@@ -3383,16 +3331,13 @@
   tree_command *
   base_parser::make_continue_command (token *continue_tok)
   {
-    int l = continue_tok->line ();
-    int c = continue_tok->column ();
-
     if (! m_lexer.m_looping)
       {
         bison_error ("continue must appear within a loop");
         return nullptr;
       }
     else
-      return new tree_continue_command (l, c);
+      return new tree_continue_command (*continue_tok);
   }
 
   // Build a return command.
@@ -3400,10 +3345,7 @@
   tree_command *
   base_parser::make_return_command (token *return_tok)
   {
-    int l = return_tok->line ();
-    int c = return_tok->column ();
-
-    return new tree_return_command (l, c);
+    return new tree_return_command (*return_tok);
   }
 
   // Build an spmd command.
@@ -3414,12 +3356,7 @@
     tree_spmd_command *retval = nullptr;
 
     if (end_token_ok (end_tok, token::spmd_end))
-      {
-        int l = spmd_tok->line ();
-        int c = spmd_tok->column ();
-
-        retval = new tree_spmd_command (body, l, c);
-      }
+      retval = new tree_spmd_command (*spmd_tok, body, *end_tok);
     else
       {
         delete body;
@@ -3452,18 +3389,7 @@
 
         token if_tok = list->if_token ();
 
-        int l = if_tok.line ();
-        int c = if_tok.column ();
-
-        tree_if_clause *elt = list->front ();
-
-        if (elt)
-          {
-            elt->line (l);
-            elt->column (c);
-          }
-
-        retval = new tree_if_command (if_tok, list, *end_tok, l, c);
+        retval = new tree_if_command (if_tok, list, *end_tok);
       }
     else
       {
@@ -3488,10 +3414,7 @@
         maybe_convert_to_braindead_shortcircuit (expr);
       }
 
-    int l = tok->line ();
-    int c = tok->column ();
-
-    return new tree_if_clause (*tok, expr, list, l, c);
+    return new tree_if_clause (*tok, expr, list);
   }
 
   tree_if_command_list *
@@ -3508,23 +3431,7 @@
     tree_switch_command *retval = nullptr;
 
     if (end_token_ok (end_tok, token::switch_end))
-      {
-        int l = switch_tok->line ();
-        int c = switch_tok->column ();
-
-        if (list && ! list->empty ())
-          {
-            tree_switch_case *elt = list->front ();
-
-            if (elt)
-              {
-                elt->line (l);
-                elt->column (c);
-              }
-          }
-
-        retval = new tree_switch_command (*switch_tok, expr, list, *end_tok, l, c);
-      }
+      retval = new tree_switch_command (*switch_tok, expr, list, *end_tok);
     else
       {
         delete expr;
@@ -3549,19 +3456,13 @@
   {
     maybe_warn_variable_switch_label (expr);
 
-    int l = case_tok->line ();
-    int c = case_tok->column ();
-
-    return new tree_switch_case (*case_tok, expr, list, l, c);
+    return new tree_switch_case (*case_tok, expr, list);
   }
 
   tree_switch_case *
   base_parser::make_default_switch_case (token *default_tok, tree_statement_list *list)
   {
-    int l = default_tok->line ();
-    int c = default_tok->column ();
-
-    return new tree_switch_case (*default_tok, list, l, c);
+    return new tree_switch_case (*default_tok, list);
   }
 
   tree_switch_case_list *
@@ -3637,9 +3538,6 @@
         break;
       }
 
-    int l = eq_tok->line ();
-    int c = eq_tok->column ();
-
     if (! lhs->is_simple_assign_lhs () && t != octave_value::op_asn_eq)
       {
         // Multiple assignments like [x,y] OP= rhs are only valid for
@@ -3679,7 +3577,7 @@
 
         m_lexer.mark_as_variable (tmp->name ());
 
-        return new tree_simple_assignment (tmp, rhs, false, l, c, t);
+        return new tree_simple_assignment (tmp, rhs, false, t);
       }
     else
       {
@@ -3701,7 +3599,7 @@
 
         m_lexer.mark_as_variables (names);
 
-        return new tree_multi_assignment (lhs, rhs, false, l, c);
+        return new tree_multi_assignment (lhs, rhs, false);
       }
   }
 
@@ -3836,13 +3734,9 @@
     else
       doc_string = leading_doc_comment.text ();
 
-    int l = fcn_tok->line ();
-    int c = fcn_tok->column ();
-
-    octave_user_function *tmp_fcn
-      = start_function (id, param_list, body, end_fcn_stmt, doc_string);
-
-    tree_function_def *retval = finish_function (fcn_tok, ret_list, eq_tok, tmp_fcn, l, c);
+    octave_user_function *tmp_fcn = start_function (id, param_list, body, end_fcn_stmt, doc_string);
+
+    tree_function_def *retval = finish_function (fcn_tok, ret_list, eq_tok, tmp_fcn);
 
     recover_from_parsing_function ();
 
@@ -3862,8 +3756,6 @@
 
     std::string id_name = id->name ();
 
-    delete id;
-
     if (m_lexer.m_parsing_classdef_get_method)
       id_name.insert (0, "get.");
     else if (m_lexer.m_parsing_classdef_set_method)
@@ -3878,11 +3770,7 @@
     body->push_back (end_fcn_stmt);
 
     octave_user_function *fcn
-      = new octave_user_function (m_lexer.m_symtab_context.curr_scope (),
-                                  param_list, nullptr, body);
-
-    fcn->stash_fcn_end_location (end_fcn_stmt->line (),
-                                 end_fcn_stmt->column ());
+      = new octave_user_function (m_lexer.m_symtab_context.curr_scope (), id, param_list, nullptr, body);
 
     // If input is coming from a file, issue a warning if the name of
     // the file does not match the name of the function stated in the
@@ -3981,17 +3869,13 @@
   }
 
   tree_statement *
-  base_parser::make_end (const std::string& type, bool eof, token *end_tok,
-                         const filepos& beg_pos, const filepos& /*end_pos*/)
+  base_parser::make_end (const std::string& type, bool eof, token *end_tok)
   {
-    int l = beg_pos.line ();
-    int c = beg_pos.column ();
-
-    return make_statement (new tree_no_op_command (type, eof, *end_tok, l, c));
+    return make_statement (new tree_no_op_command (type, eof, *end_tok));
   }
 
   tree_function_def *
-  base_parser::finish_function (token *fcn_tok, tree_parameter_list *ret_list, token *eq_tok, octave_user_function *fcn, int l, int c)
+  base_parser::finish_function (token *fcn_tok, tree_parameter_list *ret_list, token *eq_tok, octave_user_function *fcn)
   {
     tree_function_def *retval = nullptr;
 
@@ -4024,8 +3908,6 @@
 
         if (m_curr_fcn_depth > 0 || m_parsing_subfunctions)
           {
-            fcn->stash_fcn_location (l, c);
-
             octave_value ov_fcn (fcn);
 
             if (m_endfunction_found && m_function_scopes.size () > 1)
@@ -4077,7 +3959,7 @@
                 m_lexer.m_buffer_function_text = false;
               }
 
-            retval = new tree_function_def (fcn, l, c);
+            retval = new tree_function_def (fcn);
           }
       }
 
@@ -4106,14 +3988,7 @@
     tree_arguments_block *retval = nullptr;
 
     if (end_token_ok (end_tok, token::arguments_end))
-      {
-        filepos beg_pos = arguments_tok->beg_pos ();
-
-        int l = beg_pos.line ();
-        int c = beg_pos.column ();
-
-        retval = new tree_arguments_block (attr_list, validation_list, l, c);
-      }
+      retval = new tree_arguments_block (*arguments_tok, attr_list, validation_list, *end_tok);
     else
       {
         delete attr_list;
@@ -4235,13 +4110,10 @@
       {
         if (end_token_ok (end_tok, token::classdef_end))
           {
-            int l = cdef_tok->line ();
-            int c = cdef_tok->column ();
-
             if (! body)
               body = new tree_classdef_body ();
 
-            retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (), *cdef_tok, a, id, sc, body, *end_tok, m_curr_package_name, full_name, l, c);
+            retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (), *cdef_tok, a, id, sc, body, *end_tok, m_curr_package_name, full_name);
           }
         else
           {
@@ -4264,9 +4136,6 @@
 
     if (end_token_ok (end_tok, token::properties_end))
       {
-        int l = tok->line ();
-        int c = tok->column ();
-
         if (plist)
           {
             // If the element at the end of the list doesn't have a doc
@@ -4292,7 +4161,7 @@
         else
           plist = new tree_classdef_property_list ();
 
-        retval = new tree_classdef_properties_block (*tok, a, plist, *end_tok, l, c);
+        retval = new tree_classdef_properties_block (*tok, a, plist, *end_tok);
       }
     else
       {
@@ -4329,13 +4198,10 @@
 
     if (end_token_ok (end_tok, token::methods_end))
       {
-        int l = tok->line ();
-        int c = tok->column ();
-
         if (! mlist)
           mlist = new tree_classdef_method_list ();
 
-        retval = new tree_classdef_methods_block (*tok, a, mlist, *end_tok, l, c);
+        retval = new tree_classdef_methods_block (*tok, a, mlist, *end_tok);
       }
     else
       {
@@ -4355,13 +4221,10 @@
 
     if (end_token_ok (end_tok, token::events_end))
       {
-        int l = tok->line ();
-        int c = tok->column ();
-
         if (! elist)
           elist = new tree_classdef_event_list ();
 
-        retval = new tree_classdef_events_block (*tok, a, elist, *end_tok, l, c);
+        retval = new tree_classdef_events_block (*tok, a, elist, *end_tok);
       }
     else
       {
@@ -4393,13 +4256,10 @@
 
     if (end_token_ok (end_tok, token::enumeration_end))
       {
-        int l = tok->line ();
-        int c = tok->column ();
-
         if (! elist)
           elist = new tree_classdef_enum_list ();
 
-        retval = new tree_classdef_enum_block (*tok, a, elist, *end_tok, l, c);
+        retval = new tree_classdef_enum_block (*tok, a, elist, *end_tok);
       }
     else
       {
@@ -4552,8 +4412,7 @@
   }
 
   octave_user_function*
-  base_parser::start_classdef_external_method (tree_identifier *id,
-                                               tree_parameter_list *pl)
+  base_parser::start_classdef_external_method (tree_identifier *id, tree_parameter_list *pl)
   {
     octave_user_function* retval = nullptr;
 
@@ -4562,7 +4421,6 @@
 
     if (! m_curr_class_name.empty ())
       {
-
         std::string mname = id->name ();
 
         // Methods that cannot be declared outside the classdef file:
@@ -4577,14 +4435,9 @@
             // Create a dummy function that is used until the real method
             // is loaded.
 
-            retval = new octave_user_function (symbol_scope::anonymous (), pl);
+            retval = new octave_user_function (symbol_scope::anonymous (), id, pl);
 
             retval->stash_function_name (mname);
-
-            int l = id->line ();
-            int c = id->column ();
-
-            retval->stash_fcn_location (l, c);
           }
         else
           bison_error ("invalid external method declaration, an external "
@@ -4594,9 +4447,6 @@
     else
       bison_error ("external methods are only allowed in @-folders");
 
-    if (! retval)
-      delete id;
-
     return retval;
   }
 
@@ -4611,10 +4461,7 @@
     if (eq_tok)
       fcn->set_eq_tok (*eq_tok);
 
-    int l = fcn->beginning_line ();
-    int c = fcn->beginning_column ();
-
-    return new tree_function_def (fcn, l, c);
+    return new tree_function_def (fcn);
   }
 
   tree_classdef_method_list *
@@ -4720,7 +4567,10 @@
   {
     tree_index_expression *retval = nullptr;
 
-    if (args && args->has_magic_tilde ())
+    if (! args)
+      args = new tree_argument_list ();
+
+    if (args->has_magic_tilde ())
       {
         delete expr;
         delete args;
@@ -4729,9 +4579,6 @@
       }
     else
       {
-        int l = expr->line ();
-        int c = expr->column ();
-
         if (! expr->is_postfix_indexed ())
           expr->set_postfix_index (type);
 
@@ -4745,7 +4592,7 @@
             retval->append (tmp_open_delim, args, tmp_close_delim, type);
           }
         else
-          retval = new tree_index_expression (expr, tmp_open_delim, args, tmp_close_delim, l, c, type);
+          retval = new tree_index_expression (expr, tmp_open_delim, args, tmp_close_delim, type);
       }
 
     return retval;
@@ -4758,9 +4605,6 @@
   {
     tree_index_expression *retval = nullptr;
 
-    int l = expr->line ();
-    int c = expr->column ();
-
     if (! expr->is_postfix_indexed ())
       expr->set_postfix_index ('.');
 
@@ -4771,7 +4615,7 @@
         retval->append (*dot_tok, *struct_elt_tok);
       }
     else
-      retval = new tree_index_expression (expr, *dot_tok, *struct_elt_tok, l, c);
+      retval = new tree_index_expression (expr, *dot_tok, *struct_elt_tok);
 
     m_lexer.m_looking_at_indirect_ref = false;
 
@@ -4785,9 +4629,6 @@
   {
     tree_index_expression *retval = nullptr;
 
-    int l = expr->line ();
-    int c = expr->column ();
-
     if (! expr->is_postfix_indexed ())
       expr->set_postfix_index ('.');
 
@@ -4798,7 +4639,7 @@
         retval->append (*dot_tok, *open_paren, elt, *close_paren);
       }
     else
-      retval = new tree_index_expression (expr, *dot_tok, *open_paren, elt, *close_paren, l, c);
+      retval = new tree_index_expression (expr, *dot_tok, *open_paren, elt, *close_paren);
 
     m_lexer.m_looking_at_indirect_ref = false;
 
@@ -4812,9 +4653,6 @@
   {
     tree_decl_command *retval = nullptr;
 
-    int l = tok->line ();
-    int c = tok->column ();
-
     if (lst)
       m_lexer.mark_as_variables (lst->variable_names ());
 
@@ -4822,7 +4660,7 @@
       {
       case GLOBAL:
         {
-          retval = new tree_decl_command ("global", lst, l, c);
+          retval = new tree_decl_command ("global", *tok, lst);
           retval->mark_global ();
         }
         break;
@@ -4830,16 +4668,19 @@
       case PERSISTENT:
         if (m_curr_fcn_depth >= 0)
           {
-            retval = new tree_decl_command ("persistent", lst, l, c);
+            retval = new tree_decl_command ("persistent", *tok, lst);
             retval->mark_persistent ();
           }
         else
           {
+            filepos pos = tok->beg_pos ();
+            int line = pos.line ();
+
             if (m_lexer.m_reading_script_file)
               warning ("ignoring persistent declaration near line %d of file '%s'",
-                       l, m_lexer.m_fcn_file_full_name.c_str ());
+                       line, m_lexer.m_fcn_file_full_name.c_str ());
             else
-              warning ("ignoring persistent declaration near line %d", l);
+              warning ("ignoring persistent declaration near line %d", line);
           }
         break;
 
@@ -5032,8 +4873,6 @@
 
     array_list->mark_in_delims (*open_delim, *close_delim);
 
-    array_list->set_location (close_delim->line (), close_delim->column ());
-
     if (array_list->all_elements_are_constant ())
       {
         interpreter& interp = m_lexer.m_interpreter;
@@ -5062,17 +4901,17 @@
 
             if (msg.empty ())
               {
-                tree_constant *tc_retval
-                  = new tree_constant (tmp, close_delim->line (),
-                                       close_delim->column ());
-
                 std::ostringstream buf;
 
                 tree_print_code tpc (buf);
 
                 array_list->accept (tpc);
 
-                tc_retval->stash_original_text (buf.str ());
+                std::string orig_text = buf.str ();
+
+                token tok (CONSTANT, tmp, orig_text, open_delim->beg_pos (), close_delim->end_pos ());
+
+                tree_constant *tc_retval = new tree_constant (tmp, orig_text, tok);
 
                 delete array_list;
 
@@ -5093,10 +4932,15 @@
   tree_expression *
   base_parser::finish_matrix (token *open_delim, tree_matrix *m, token *close_delim)
   {
-    return (m
-            ? finish_array_list (open_delim, m, close_delim)
-            : new tree_constant (octave_null_matrix::instance,
-                                 close_delim->line (), close_delim->column ()));
+    if (m)
+      return finish_array_list (open_delim, m, close_delim);
+
+    octave_value tmp {octave_null_matrix::instance};
+    std::string orig_text {"{}"};
+
+    token tok (CONSTANT, tmp, orig_text, open_delim->beg_pos (), close_delim->end_pos ());
+
+    return new tree_constant (tmp, orig_text, tok);
   }
 
   tree_matrix *
@@ -5119,10 +4963,15 @@
   tree_expression *
   base_parser::finish_cell (token *open_delim, tree_cell *c, token *close_delim)
   {
-    return (c
-            ? finish_array_list (open_delim, c, close_delim)
-            : new tree_constant (octave_value (Cell ()),
-                                 close_delim->line (), close_delim->column ()));
+    if (c)
+      return finish_array_list (open_delim, c, close_delim);
+
+    octave_value tmp {Cell ()};
+    std::string orig_text {"{}"};
+
+    token tok (CONSTANT, tmp, orig_text, open_delim->beg_pos (), close_delim->end_pos ());
+
+    return new tree_constant (tmp, orig_text, tok);
   }
 
   tree_cell *
@@ -5154,10 +5003,7 @@
     std::string meth = superclassref->superclass_method_name ();
     std::string cls = superclassref->superclass_class_name ();
 
-    int l = superclassref->line ();
-    int c = superclassref->column ();
-
-    return new tree_superclass_ref (meth, cls, l, c);
+    return new tree_superclass_ref (meth, cls, *superclassref);
   }
 
   tree_metaclass_query *
@@ -5165,10 +5011,7 @@
   {
     std::string cls = metaquery->text ();
 
-    int l = metaquery->line ();
-    int c = metaquery->column ();
-
-    return new tree_metaclass_query (cls, l, c);
+    return new tree_metaclass_query (cls, *metaquery);
   }
 
   tree_statement_list *
--- a/libinterp/parse-tree/parse.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/parse.h	Thu Apr 04 01:00:08 2024 -0400
@@ -252,8 +252,7 @@
 
   // Build an anonymous function handle.
   OCTINTERP_API tree_anon_fcn_handle *
-  make_anon_fcn_handle (tree_parameter_list *param_list,
-                        tree_expression *expr, const filepos& at_pos);
+  make_anon_fcn_handle (token *at_tok, tree_parameter_list *param_list, tree_expression *expr);
 
   // Build a colon expression.
   OCTINTERP_API tree_expression *
@@ -373,7 +372,7 @@
 
   // Create a no-op statement for end_function.
   OCTINTERP_API tree_statement *
-  make_end (const std::string& type, bool eof, token *tok, const filepos& beg_pos, const filepos& end_pos);
+  make_end (const std::string& type, bool eof, token *tok);
 
   // Do most of the work for defining a function.
   OCTINTERP_API octave_user_function *
@@ -381,7 +380,7 @@
 
   // Finish defining a function.
   OCTINTERP_API tree_function_def *
-  finish_function (token *fcn_tok, tree_parameter_list *ret_list, token *eq_tok, octave_user_function *fcn, int l, int c);
+  finish_function (token *fcn_tok, tree_parameter_list *ret_list, token *eq_tok, octave_user_function *fcn);
 
   OCTINTERP_API tree_statement_list *
   append_function_body (tree_statement_list *body, tree_statement_list *list);
--- a/libinterp/parse-tree/pt-arg-list.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-arg-list.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -143,6 +143,8 @@
   tree_argument_list *new_list = new tree_argument_list ();
 
   new_list->m_simple_assign_lhs = m_simple_assign_lhs;
+  new_list->m_list_includes_magic_tilde = m_list_includes_magic_tilde;
+  new_list->m_delims = m_delims;
 
   for (const tree_expression *elt : *this)
     new_list->push_back (elt ? elt->dup (scope) : nullptr);
--- a/libinterp/parse-tree/pt-arg-list.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-arg-list.h	Thu Apr 04 01:00:08 2024 -0400
@@ -36,6 +36,7 @@
 #include "str-vec.h"
 
 #include "pt-delimiter-list.h"
+#include "pt-exp.h"
 #include "pt-walk.h"
 #include "token.h"
 
@@ -54,13 +55,9 @@
 
   typedef tree_expression *element_type;
 
-  tree_argument_list ()
-    : m_list_includes_magic_tilde (false), m_simple_assign_lhs (false)
-  { }
+  tree_argument_list () { }
 
-  tree_argument_list (tree_expression *t)
-    : m_list_includes_magic_tilde (false), m_simple_assign_lhs (false)
-  { push_back (t); }
+  tree_argument_list (tree_expression *t) { push_back (t); }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_argument_list)
 
@@ -72,6 +69,34 @@
     return this;
   }
 
+  filepos beg_pos () const
+  {
+    if (m_delims.empty ())
+      {
+        if (empty ())
+          return filepos ();
+
+        tree_expression *elt = front ();
+        return elt->beg_pos ();
+      }
+
+    return m_delims.beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (m_delims.empty ())
+      {
+        if (empty ())
+          return filepos ();
+
+        tree_expression *elt = back ();
+        return elt->end_pos ();
+      }
+
+    return m_delims.end_pos ();
+  }
+
   bool has_magic_tilde () const
   {
     return m_list_includes_magic_tilde;
@@ -113,9 +138,9 @@
 
 private:
 
-  bool m_list_includes_magic_tilde;
+  bool m_list_includes_magic_tilde {false};
 
-  bool m_simple_assign_lhs;
+  bool m_simple_assign_lhs {false};
 
   tree_delimiter_list m_delims;
 };
--- a/libinterp/parse-tree/pt-args-block.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-args-block.h	Thu Apr 04 01:00:08 2024 -0400
@@ -215,8 +215,8 @@
 {
 public:
 
-  tree_arguments_block (tree_args_block_attribute_list *attr_list, tree_args_block_validation_list *validation_list, int l = -1, int c = -1)
-    : tree_command (l, c), m_attr_list (attr_list), m_validation_list (validation_list)
+  tree_arguments_block (const token& args_tok, tree_args_block_attribute_list *attr_list, tree_args_block_validation_list *validation_list, const token& end_tok)
+    : m_args_tok (args_tok), m_attr_list (attr_list), m_validation_list (validation_list), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_arguments_block)
@@ -227,6 +227,9 @@
     delete m_validation_list;
   }
 
+  filepos beg_pos () const { return m_args_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_args_block_attribute_list * attribute_list ()
   {
     return m_attr_list;
@@ -244,9 +247,13 @@
 
 private:
 
+  token m_args_tok;
+
   tree_args_block_attribute_list *m_attr_list;
 
   tree_args_block_validation_list *m_validation_list;
+
+  token m_end_tok;
 };
 
 OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/pt-array-list.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-array-list.h	Thu Apr 04 01:00:08 2024 -0400
@@ -50,8 +50,7 @@
 
 protected:
 
-  tree_array_list (tree_argument_list *row = nullptr, int l = -1, int c = -1)
-    : tree_expression (l, c), std::list<tree_argument_list *> ()
+  tree_array_list (tree_argument_list *row = nullptr)
   {
     if (row)
       push_back (row);
@@ -63,6 +62,12 @@
 
   ~tree_array_list ();
 
+  // The delimiter list for a cell array should never be empty.  But
+  // better safe than sorry, I guess.
+
+  filepos beg_pos () const { return m_delims.empty () ? filepos () : m_delims.beg_pos (); }
+  filepos end_pos () const { return m_delims.empty () ? filepos () : m_delims.end_pos (); }
+
   bool all_elements_are_constant () const;
 
   // FIXME: should we import the functions from the base class and
--- a/libinterp/parse-tree/pt-assign.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-assign.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -40,12 +40,8 @@
 
 // Simple assignment expressions.
 
-tree_simple_assignment::tree_simple_assignment (tree_expression *le,
-    tree_expression *re,
-    bool plhs, int l, int c,
-    octave_value::assign_op t)
-  : tree_expression (l, c), m_lhs (le), m_rhs (re), m_preserve (plhs),
-    m_ans_assign (), m_etype (t)
+tree_simple_assignment::tree_simple_assignment (tree_expression *le, tree_expression *re, bool plhs, octave_value::assign_op t)
+  : m_lhs (le), m_rhs (re), m_preserve (plhs), m_ans_assign (), m_etype (t)
 { }
 
 tree_simple_assignment::~tree_simple_assignment ()
@@ -153,10 +149,8 @@
 
 // Multi-valued assignment expressions.
 
-tree_multi_assignment::tree_multi_assignment (tree_argument_list *lst,
-    tree_expression *r,
-    bool plhs, int l, int c)
-  : tree_expression (l, c), m_lhs (lst), m_rhs (r), m_preserve (plhs)
+tree_multi_assignment::tree_multi_assignment (tree_argument_list *lst, tree_expression *r, bool plhs)
+  : m_lhs (lst), m_rhs (r), m_preserve (plhs)
 { }
 
 tree_multi_assignment::~tree_multi_assignment ()
--- a/libinterp/parse-tree/pt-assign.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-assign.h	Thu Apr 04 01:00:08 2024 -0400
@@ -36,6 +36,7 @@
 
 #include "comment-list.h"
 #include "ov.h"
+#include "pt-arg-list.h"
 #include "pt-exp.h"
 #include "pt-walk.h"
 #include "token.h"
@@ -52,15 +53,7 @@
 {
 public:
 
-  tree_simple_assignment (bool plhs = false, int l = -1, int c = -1,
-                          octave_value::assign_op t = octave_value::op_asn_eq)
-    : tree_expression (l, c), m_lhs (nullptr), m_rhs (nullptr),
-      m_preserve (plhs), m_ans_assign (), m_etype (t)
-  { }
-
-  tree_simple_assignment (tree_expression *le, tree_expression *re,
-                          bool plhs = false, int l = -1, int c = -1,
-                          octave_value::assign_op t = octave_value::op_asn_eq);
+  tree_simple_assignment (tree_expression *le, tree_expression *re, bool plhs = false, octave_value::assign_op t = octave_value::op_asn_eq);
 
   OCTAVE_DISABLE_COPY_MOVE (tree_simple_assignment)
 
@@ -68,6 +61,9 @@
 
   comment_list leading_comments () const { return m_lhs->leading_comments (); }
 
+  filepos beg_pos () const { return m_lhs->beg_pos (); }
+  filepos end_pos () const { return m_rhs->end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   bool is_assignment_expression () const { return true; }
@@ -123,13 +119,11 @@
 {
 public:
 
-  tree_multi_assignment (bool plhs = false, int l = -1, int c = -1)
-    : tree_expression (l, c), m_lhs (nullptr), m_rhs (nullptr),
-      m_preserve (plhs)
+  tree_multi_assignment (bool plhs = false)
+    : m_lhs (nullptr), m_rhs (nullptr), m_preserve (plhs)
   { }
 
-  tree_multi_assignment (tree_argument_list *lst, tree_expression *r,
-                         bool plhs = false, int l = -1, int c = -1);
+  tree_multi_assignment (tree_argument_list *lst, tree_expression *r, bool plhs = false);
 
   OCTAVE_DISABLE_COPY_MOVE (tree_multi_assignment)
 
@@ -137,6 +131,9 @@
 
   bool is_assignment_expression () const { return true; }
 
+  filepos beg_pos () const { return m_lhs->beg_pos (); }
+  filepos end_pos () const { return m_rhs->end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   std::string oper () const;
--- a/libinterp/parse-tree/pt-binop.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-binop.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -58,8 +58,9 @@
 {
   tree_binary_expression *new_be
     = new tree_binary_expression (m_lhs ? m_lhs->dup (scope) : nullptr,
+                                  m_op_tok,
                                   m_rhs ? m_rhs->dup (scope) : nullptr,
-                                  line (), column (), m_etype);
+                                  m_etype);
 
   new_be->copy_base (*this);
 
@@ -108,8 +109,9 @@
   tree_braindead_shortcircuit_binary_expression *new_be
     = new tree_braindead_shortcircuit_binary_expression
   (m_lhs ? m_lhs->dup (scope) : nullptr,
+   m_op_tok,
    m_rhs ? m_rhs->dup (scope) : nullptr,
-   line (), column (), op_type ());
+   op_type ());
 
   new_be->copy_base (*this);
 
@@ -117,8 +119,7 @@
 }
 
 octave_value
-tree_braindead_shortcircuit_binary_expression::evaluate (tree_evaluator& tw,
-    int)
+tree_braindead_shortcircuit_binary_expression::evaluate (tree_evaluator& tw, int)
 {
   if (m_lhs)
     {
@@ -194,8 +195,9 @@
 {
   tree_boolean_expression *new_be
     = new tree_boolean_expression (m_lhs ? m_lhs->dup (scope) : nullptr,
+                                   m_op_tok,
                                    m_rhs ? m_rhs->dup (scope) : nullptr,
-                                   line (), column (), m_etype);
+                                   m_etype);
 
   new_be->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-binop.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-binop.h	Thu Apr 04 01:00:08 2024 -0400
@@ -47,19 +47,12 @@
 {
 public:
 
-  tree_binary_expression (int l = -1, int c = -1,
-                          octave_value::binary_op t
-                          = octave_value::unknown_binary_op)
-    : tree_expression (l, c), m_lhs (nullptr), m_rhs (nullptr), m_etype (t),
-      m_preserve_operands (false)
+  tree_binary_expression (octave_value::binary_op t = octave_value::unknown_binary_op)
+    : m_lhs (nullptr), m_rhs (nullptr), m_etype (t), m_preserve_operands (false)
   { }
 
-  tree_binary_expression (tree_expression *a, tree_expression *b,
-                          int l = -1, int c = -1,
-                          octave_value::binary_op t
-                          = octave_value::unknown_binary_op)
-    : tree_expression (l, c), m_lhs (a), m_rhs (b), m_etype (t),
-      m_preserve_operands (false)
+  tree_binary_expression (tree_expression *a, const token& op_tok, tree_expression *b, octave_value::binary_op t = octave_value::unknown_binary_op)
+    : m_lhs (a), m_op_tok (op_tok), m_rhs (b), m_etype (t), m_preserve_operands (false)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_binary_expression)
@@ -73,10 +66,15 @@
       }
   }
 
+  token operator_token () const { return m_op_tok; }
+
   void preserve_operands () { m_preserve_operands = true; }
 
   bool is_binary_expression () const { return true; }
 
+  filepos beg_pos () const { return m_lhs->beg_pos (); }
+  filepos end_pos () const { return m_rhs->end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   std::string oper () const;
@@ -108,10 +106,14 @@
   void matlab_style_short_circuit_warning (const char *op);
 
   virtual bool is_braindead () const { return false; }
+
 protected:
 
-  // The operands for the expression.
+  // The operands and operator for the expression.
   tree_expression *m_lhs;
+
+  token m_op_tok;
+
   tree_expression *m_rhs;
 
 private:
@@ -128,11 +130,8 @@
 {
 public:
 
-  tree_braindead_shortcircuit_binary_expression (tree_expression *a,
-                                                 tree_expression *b,
-                                                 int l, int c,
-                                                 octave_value::binary_op t)
-    : tree_binary_expression (a, b, l, c, t)
+  tree_braindead_shortcircuit_binary_expression (tree_expression *a, const token& op_tok, tree_expression *b, octave_value::binary_op t)
+    : tree_binary_expression (a, op_tok, b, t)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_braindead_shortcircuit_binary_expression)
@@ -161,12 +160,11 @@
     bool_or
   };
 
-  tree_boolean_expression (int l = -1, int c = -1, type t = unknown)
-    : tree_binary_expression (l, c), m_etype (t) { }
+  tree_boolean_expression (type t = unknown) : m_etype (t) { }
 
-  tree_boolean_expression (tree_expression *a, tree_expression *b,
-                           int l = -1, int c = -1, type t = unknown)
-    : tree_binary_expression (a, b, l, c), m_etype (t) { }
+  tree_boolean_expression (tree_expression *a, const token& op_tok, tree_expression *b, type t = unknown)
+    : tree_binary_expression (a, op_tok, b), m_etype (t)
+  { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_boolean_expression)
 
--- a/libinterp/parse-tree/pt-cbinop.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-cbinop.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -140,8 +140,7 @@
 // Possibly contract and/or with negation.
 
 tree_binary_expression *
-maybe_compound_binary_expression (tree_expression *a, tree_expression *b,
-                                  int l, int c, octave_value::binary_op t)
+maybe_compound_binary_expression (tree_expression *a, const token& op_tok, tree_expression *b, octave_value::binary_op t)
 {
   tree_expression *ca = a;
   tree_expression *cb = b;
@@ -164,8 +163,8 @@
 
   tree_binary_expression *ret
     = (ct == octave_value::unknown_compound_binary_op
-       ? new tree_binary_expression (a, b, l, c, t)
-       : new tree_compound_binary_expression (a, b, l, c, t, ca, cb, ct));
+       ? new tree_binary_expression (a, op_tok, b, t)
+       : new tree_compound_binary_expression (a, op_tok, b, t, ca, cb, ct));
 
   return ret;
 }
--- a/libinterp/parse-tree/pt-cbinop.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-cbinop.h	Thu Apr 04 01:00:08 2024 -0400
@@ -45,13 +45,9 @@
 {
 public:
 
-  tree_compound_binary_expression (tree_expression *a, tree_expression *b,
-                                   int l, int c,
-                                   octave_value::binary_op t,
-                                   tree_expression *ca, tree_expression *cb,
-                                   octave_value::compound_binary_op ct)
-    : tree_binary_expression (a, b, l, c, t), m_lhs (ca), m_rhs (cb),
-      m_etype (ct)
+  tree_compound_binary_expression (tree_expression *a, const token& op_tok, tree_expression *b, octave_value::binary_op t,
+                                   tree_expression *ca, tree_expression *cb, octave_value::compound_binary_op ct)
+    : tree_binary_expression (a, op_tok, b, t), m_lhs (ca), m_rhs (cb), m_etype (ct)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_compound_binary_expression)
@@ -89,10 +85,7 @@
 // a "virtual constructor"
 
 tree_binary_expression *
-maybe_compound_binary_expression (tree_expression *a, tree_expression *b,
-                                  int l = -1, int c = -1,
-                                  octave_value::binary_op t
-                                  = octave_value::unknown_binary_op);
+maybe_compound_binary_expression (tree_expression *a, const token& op_tok, tree_expression *b, octave_value::binary_op t = octave_value::unknown_binary_op);
 
 OCTAVE_END_NAMESPACE(octave)
 
--- a/libinterp/parse-tree/pt-cell.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-cell.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -41,7 +41,7 @@
 tree_expression *
 tree_cell::dup (symbol_scope& scope) const
 {
-  tree_cell *new_cell = new tree_cell (nullptr, line (), column ());
+  tree_cell *new_cell = new tree_cell (nullptr);
 
   new_cell->copy_base (*this, scope);
 
--- a/libinterp/parse-tree/pt-cell.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-cell.h	Thu Apr 04 01:00:08 2024 -0400
@@ -47,8 +47,8 @@
 {
 public:
 
-  tree_cell (tree_argument_list *row = nullptr, int l = -1, int c = -1)
-    : tree_array_list (row, l, c)
+  tree_cell (tree_argument_list *row = nullptr)
+    : tree_array_list (row)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_cell)
--- a/libinterp/parse-tree/pt-classdef.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-classdef.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -41,9 +41,7 @@
 tree_superclass_ref *
 tree_superclass_ref::dup (symbol_scope&) const
 {
-  tree_superclass_ref *new_scr
-    = new tree_superclass_ref (m_method_name, m_class_name,
-                               line (), column ());
+  tree_superclass_ref *new_scr = new tree_superclass_ref (m_method_name, m_class_name, m_token);
 
   new_scr->copy_base (*this);
 
@@ -53,8 +51,7 @@
 octave_value_list
 tree_superclass_ref::evaluate_n (tree_evaluator& tw, int nargout)
 {
-  octave_value tmp
-    = octave_classdef::superclass_ref (m_method_name, m_class_name);
+  octave_value tmp = octave_classdef::superclass_ref (m_method_name, m_class_name);
 
   if (! is_postfix_indexed ())
     {
@@ -78,8 +75,7 @@
 tree_metaclass_query *
 tree_metaclass_query::dup (symbol_scope&) const
 {
-  tree_metaclass_query *new_mcq
-    = new tree_metaclass_query (m_class_name, line (), column ());
+  tree_metaclass_query *new_mcq = new tree_metaclass_query (m_class_name, m_token);
 
   new_mcq->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-classdef.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-classdef.h	Thu Apr 04 01:00:08 2024 -0400
@@ -49,15 +49,17 @@
 {
 public:
 
-  tree_superclass_ref (const std::string& meth, const std::string& cls,
-                       int l = -1, int c = -1)
-    : tree_expression (l, c), m_method_name (meth), m_class_name (cls)
+  tree_superclass_ref (const std::string& meth, const std::string& cls, const token& tok)
+    : m_method_name (meth), m_class_name (cls), m_token (tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_superclass_ref)
 
   ~tree_superclass_ref () = default;
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
   std::string method_name () const
   {
     return m_method_name;
@@ -90,20 +92,25 @@
   // The name of the superclass.  This is the text after the "@"
   // and may be of the form "object.method".
   std::string m_class_name;
+
+  token m_token;
 };
 
 class tree_metaclass_query : public tree_expression
 {
 public:
 
-  tree_metaclass_query (const std::string& cls, int l = -1, int c = -1)
-    : tree_expression (l, c), m_class_name (cls)
+  tree_metaclass_query (const std::string& cls, const token& tok)
+    : m_class_name (cls), m_token (tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_metaclass_query)
 
   ~tree_metaclass_query () = default;
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
   std::string class_name () const { return m_class_name; }
 
   tree_metaclass_query * dup (symbol_scope& scope) const;
@@ -123,6 +130,8 @@
 private:
 
   std::string m_class_name;
+
+  token m_token;
 };
 
 class tree_classdef_attribute
@@ -149,6 +158,9 @@
     delete m_expr;
   }
 
+  filepos beg_pos () const { return m_not_tok ? m_not_tok.beg_pos () : m_id->beg_pos (); }
+  filepos end_pos () const { return m_expr ? m_expr->end_pos () : m_id->end_pos (); }
+
   tree_identifier * ident () { return m_id; }
 
   tree_expression * expression () { return m_expr; }
@@ -263,8 +275,8 @@
 {
 public:
 
-  tree_base_classdef_block (const token& block_tok, tree_classdef_attribute_list *a, const token& end_tok, int l = -1, int c = -1)
-    : tree (l, c), m_block_tok (block_tok), m_attr_list (a), m_end_tok (end_tok)
+  tree_base_classdef_block (const token& block_tok, tree_classdef_attribute_list *a, const token& end_tok)
+    : m_block_tok (block_tok), m_attr_list (a), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_base_classdef_block)
@@ -280,7 +292,7 @@
 
   void accept (tree_walker&) { }
 
-private:
+protected:
 
   token m_block_tok;
 
@@ -295,8 +307,8 @@
 {
 public:
 
-  tree_classdef_block (const token& block_tok, tree_classdef_attribute_list *a, T *elt_list, const token& end_tok, int l = -1, int c = -1)
-    : tree_base_classdef_block (block_tok, a, end_tok, l, c), m_elt_list (elt_list)
+  tree_classdef_block (const token& block_tok, tree_classdef_attribute_list *a, T *elt_list, const token& end_tok)
+    : tree_base_classdef_block (block_tok, a, end_tok), m_elt_list (elt_list)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_classdef_block)
@@ -306,6 +318,9 @@
     delete m_elt_list;
   }
 
+  filepos beg_pos () const { return m_block_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   T * element_list () { return m_elt_list; }
 
 private:
@@ -374,8 +389,8 @@
 {
 public:
 
-  tree_classdef_properties_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_property_list *plist, const token& end_tok, int l = -1, int c = -1)
-    : tree_classdef_block<tree_classdef_property_list> (block_tok, a, plist, end_tok, l, c)
+  tree_classdef_properties_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_property_list *plist, const token& end_tok)
+    : tree_classdef_block<tree_classdef_property_list> (block_tok, a, plist, end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_classdef_properties_block)
@@ -415,8 +430,8 @@
 {
 public:
 
-  tree_classdef_methods_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_method_list *mlist, const token& end_tok, int l = -1, int c = -1)
-    : tree_classdef_block<tree_classdef_method_list> (block_tok, a, mlist, end_tok, l, c)
+  tree_classdef_methods_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_method_list *mlist, const token& end_tok)
+    : tree_classdef_block<tree_classdef_method_list> (block_tok, a, mlist, end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_classdef_methods_block)
@@ -482,8 +497,8 @@
 {
 public:
 
-  tree_classdef_events_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_event_list *elist, const token& end_tok, int l = -1, int c = -1)
-    : tree_classdef_block<tree_classdef_event_list> (block_tok, a, elist, end_tok, l, c)
+  tree_classdef_events_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_event_list *elist, const token& end_tok)
+    : tree_classdef_block<tree_classdef_event_list> (block_tok, a, elist, end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_classdef_events_block)
@@ -559,8 +574,8 @@
 {
 public:
 
-  tree_classdef_enum_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_enum_list *elist, const token& end_tok, int l = -1, int c = -1)
-    : tree_classdef_block<tree_classdef_enum_list> (block_tok, a, elist, end_tok, l, c)
+  tree_classdef_enum_block (const token& block_tok, tree_classdef_attribute_list *a, tree_classdef_enum_list *elist, const token& end_tok)
+    : tree_classdef_block<tree_classdef_enum_list> (block_tok, a, elist, end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_classdef_enum_block)
@@ -681,8 +696,8 @@
 {
 public:
 
-  tree_classdef (const symbol_scope& scope, const token& cdef_tok, tree_classdef_attribute_list *a, tree_identifier *i, tree_classdef_superclass_list *sc, tree_classdef_body *b, const token& end_tok, const std::string& pn = "", const std::string& fn = "", int l = -1, int c = -1)
-    : tree_command (l, c), m_scope (scope), m_cdef_tok (cdef_tok), m_attr_list (a), m_id (i), m_supclass_list (sc), m_body (b), m_end_tok (end_tok), m_pack_name (pn), m_file_name (fn)
+  tree_classdef (const symbol_scope& scope, const token& cdef_tok, tree_classdef_attribute_list *a, tree_identifier *i, tree_classdef_superclass_list *sc, tree_classdef_body *b, const token& end_tok, const std::string& pn = "", const std::string& fn = "")
+    : m_scope (scope), m_cdef_tok (cdef_tok), m_attr_list (a), m_id (i), m_supclass_list (sc), m_body (b), m_end_tok (end_tok), m_pack_name (pn), m_file_name (fn)
   {
     cache_doc_string ();
   }
@@ -697,6 +712,9 @@
     delete m_body;
   }
 
+  filepos beg_pos () const { return m_cdef_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   symbol_scope scope () { return m_scope; }
 
   tree_classdef_attribute_list *
@@ -772,3 +790,4 @@
 OCTAVE_END_NAMESPACE(octave)
 
 #endif
+
--- a/libinterp/parse-tree/pt-cmd.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-cmd.h	Thu Apr 04 01:00:08 2024 -0400
@@ -35,6 +35,7 @@
 #include "pt.h"
 #include "pt-bp.h"
 #include "pt-walk.h"
+#include "panic.h"
 #include "token.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
@@ -45,12 +46,13 @@
 {
 public:
 
-  tree_command (int l = -1, int c = -1)
-    : tree (l, c) { }
+  tree_command () = default;
 
   OCTAVE_DISABLE_COPY_MOVE (tree_command)
 
   virtual ~tree_command () = default;
+
+  virtual void update_end_pos (const filepos&) { panic_impossible (); }
 };
 
 // No-op.
@@ -59,13 +61,25 @@
 {
 public:
 
-  tree_no_op_command (const std::string& cmd, bool eof, const token& tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_eof (eof), m_tok (tok), m_orig_cmd (cmd) { }
+  tree_no_op_command (const std::string& cmd, bool eof, const token& tok)
+    : m_eof (eof), m_tok (tok), m_orig_cmd (cmd)
+  { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_no_op_command)
 
   ~tree_no_op_command () = default;
 
+  filepos beg_pos () const { return m_tok.beg_pos (); }
+  filepos end_pos () const { return m_tok.end_pos (); }
+
+  void update_end_pos (const filepos& pos)
+  {
+    if (is_end_of_fcn_or_script () || is_end_of_file ())
+      m_tok.end_pos (pos);
+    else
+      panic_impossible ();
+  }
+
   comment_list leading_comments () const { return m_tok.leading_comments (); }
 
   void attach_trailing_comments (const comment_list& lst)
@@ -108,13 +122,24 @@
 {
 public:
 
-  tree_function_def (octave_function *f, int l = -1, int c = -1)
-    : tree_command (l, c), m_fcn (f) { }
+  tree_function_def (octave_function *f) : m_fcn (f) { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_function_def)
 
   ~tree_function_def () = default;
 
+  filepos beg_pos () const
+  {
+    octave_function *f = m_fcn.function_value ();
+    return f->beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    octave_function *f = m_fcn.function_value ();
+    return f->end_pos ();
+  }
+
   void accept (tree_walker& tw)
   {
     tw.visit_function_def (*this);
@@ -126,8 +151,7 @@
 
   octave_value m_fcn;
 
-  tree_function_def (const octave_value& v, int l = -1, int c = -1)
-    : tree_command (l, c), m_fcn (v) { }
+  tree_function_def (const octave_value& v) : m_fcn (v) { }
 };
 
 OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/pt-colon.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-colon.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -44,8 +44,7 @@
                                  m_colon_1_tok,
                                  m_increment ? m_increment->dup (scope) : nullptr,
                                  m_colon_2_tok,
-                                 m_limit ? m_limit->dup (scope) : nullptr,
-                                 line (), column ());
+                                 m_limit ? m_limit->dup (scope) : nullptr);
 
   new_ce->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-colon.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-colon.h	Thu Apr 04 01:00:08 2024 -0400
@@ -46,12 +46,12 @@
 {
 public:
 
-  tree_colon_expression (tree_expression *base, const token& colon_1_tok, tree_expression *limit, int l = -1, int c = -1)
-    : tree_expression (l, c), m_base (base), m_colon_1_tok (colon_1_tok), m_limit (limit)
+  tree_colon_expression (tree_expression *base, const token& colon_1_tok, tree_expression *limit)
+    : m_base (base), m_colon_1_tok (colon_1_tok), m_limit (limit)
   { }
 
-  tree_colon_expression (tree_expression *base, const token& colon_1_tok, tree_expression *increment, const token& colon_2_tok, tree_expression *limit, int l = -1, int c = -1)
-    : tree_expression (l, c), m_base (base), m_colon_1_tok (colon_1_tok), m_increment (increment), m_colon_2_tok (colon_2_tok), m_limit (limit)
+  tree_colon_expression (tree_expression *base, const token& colon_1_tok, tree_expression *increment, const token& colon_2_tok, tree_expression *limit)
+    : m_base (base), m_colon_1_tok (colon_1_tok), m_increment (increment), m_colon_2_tok (colon_2_tok), m_limit (limit)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_colon_expression)
@@ -65,6 +65,9 @@
     delete m_increment;
   }
 
+  filepos beg_pos () const { return m_base->beg_pos (); }
+  filepos end_pos () const { return m_limit->end_pos (); }
+
   void preserve_base () { m_save_base = true; }
 
   bool rvalue_ok () const { return true; }
--- a/libinterp/parse-tree/pt-const.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-const.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -63,8 +63,7 @@
 tree_expression *
 tree_constant::dup (symbol_scope&) const
 {
-  tree_constant *new_tc
-    = new tree_constant (m_value, m_orig_text, line (), column ());
+  tree_constant *new_tc = new tree_constant (m_value, m_orig_text, m_token);
 
   new_tc->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-const.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-const.h	Thu Apr 04 01:00:08 2024 -0400
@@ -38,6 +38,7 @@
 #include "pt-bp.h"
 #include "pt-exp.h"
 #include "pt-walk.h"
+#include "token.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
@@ -48,17 +49,12 @@
 {
 public:
 
-  tree_constant (int l = -1, int c = -1)
-    : tree_expression (l, c), m_value (), m_orig_text ()
+  tree_constant (const octave_value& v, const token& tok)
+    : m_value (v), m_token (tok)
   { }
 
-  tree_constant (const octave_value& v, int l = -1, int c = -1)
-    : tree_expression (l, c), m_value (v), m_orig_text ()
-  { }
-
-  tree_constant (const octave_value& v, const std::string& ot,
-                 int l = -1, int c = -1)
-    : tree_expression (l, c), m_value (v), m_orig_text (ot)
+  tree_constant (const octave_value& v, const std::string& ot, const token& tok)
+    : m_value (v), m_orig_text (ot), m_token (tok)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_constant)
@@ -69,6 +65,9 @@
 
   bool is_constant () const { return true; }
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
   void maybe_mutate () { m_value.maybe_mutate (); }
 
   void print (std::ostream& os, bool pr_as_read_syntax = false,
@@ -115,6 +114,8 @@
 
   // The original text form of this constant.
   std::string m_orig_text;
+
+  token m_token;
 };
 
 OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/pt-decl.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-decl.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -68,9 +68,8 @@
 
 // Declaration commands (global, static).
 
-tree_decl_command::tree_decl_command (const std::string& n,
-                                      tree_decl_init_list *t, int l, int c)
-  : tree_command (l, c), m_cmd_name (n), m_init_list (t)
+tree_decl_command::tree_decl_command (const std::string& n, const token& tok, tree_decl_init_list *t)
+  : m_cmd_name (n), m_token (tok), m_init_list (t)
 {
   if (t)
     {
--- a/libinterp/parse-tree/pt-decl.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-decl.h	Thu Apr 04 01:00:08 2024 -0400
@@ -62,6 +62,9 @@
 
   ~tree_decl_elt ();
 
+  filepos beg_pos () const { return m_id->beg_pos (); }
+  filepos end_pos () const { return m_expr ? m_expr->end_pos () : m_id->end_pos (); }
+
   void mark_as_formal_parameter ()
   {
     m_id->mark_as_formal_parameter ();
@@ -124,6 +127,24 @@
       }
   }
 
+  filepos beg_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_decl_elt *elt = front ();
+    return elt->beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_decl_elt *elt = back ();
+    return elt->end_pos ();
+  }
+
   void mark_global ()
   {
     for (tree_decl_elt *elt : *this)
@@ -163,16 +184,15 @@
 {
 public:
 
-  tree_decl_command (const std::string& n, int l = -1, int c = -1)
-    : tree_command (l, c), m_cmd_name (n), m_init_list (nullptr) { }
-
-  tree_decl_command (const std::string& n, tree_decl_init_list *t,
-                     int l = -1, int c = -1);
+  tree_decl_command (const std::string& n, const token& tok, tree_decl_init_list *t);
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_decl_command)
 
   ~tree_decl_command ();
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_init_list->end_pos (); }
+
   void mark_global ()
   {
     if (m_init_list)
@@ -199,6 +219,8 @@
   // The name of this command -- global, static, etc.
   std::string m_cmd_name;
 
+  token m_token;
+
   // The list of variables or initializers in this declaration command.
   tree_decl_init_list *m_init_list;
 };
--- a/libinterp/parse-tree/pt-delimiter-list.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-delimiter-list.h	Thu Apr 04 01:00:08 2024 -0400
@@ -30,6 +30,7 @@
 
 #include <stack>
 
+#include "filepos.h"
 #include "token.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
@@ -42,13 +43,33 @@
 
   OCTAVE_DEFAULT_CONSTRUCT_COPY_MOVE_DELETE (tree_delimiter_list)
 
-    size_t count () const { return m_delimiters.size (); }
+  size_t count () const { return m_delimiters.size (); }
+
+  bool empty () const { return m_delimiters.empty (); }
 
   void push (const token& open_delim, const token& close_delim)
   {
     m_delimiters.push (element_type (open_delim, close_delim));
   }
 
+  filepos beg_pos () const
+  {
+    if (m_delimiters.empty ())
+      return filepos ();
+
+    const element_type& elt = m_delimiters.top ();
+    return elt.first.beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (m_delimiters.empty ())
+      return filepos ();
+
+    const element_type& elt = m_delimiters.top ();
+    return elt.second.end_pos ();
+  }
+
 private:
 
   std::stack<element_type> m_delimiters;
--- a/libinterp/parse-tree/pt-eval.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-eval.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -3462,9 +3462,7 @@
         local_vars[name] = val;
     }
 
-  octave_user_function *af
-    = new octave_user_function (new_scope, param_list_dup, ret_list,
-                                stmt_list);
+  octave_user_function *af = new octave_user_function (new_scope, nullptr, param_list_dup, ret_list, stmt_list);
 
   octave_function *curr_fcn = m_call_stack.current_function ();
 
--- a/libinterp/parse-tree/pt-except.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-except.h	Thu Apr 04 01:00:08 2024 -0400
@@ -43,14 +43,17 @@
 {
 public:
 
-  tree_try_catch_command (const token try_tok, tree_statement_list *tc, const token catch_tok, tree_identifier *id, tree_statement_list *cc, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_try_tok (try_tok), m_try_code (tc), m_catch_tok (catch_tok), m_expr_id (id), m_catch_code (cc), m_end_tok (end_tok)
+  tree_try_catch_command (const token try_tok, tree_statement_list *tc, const token catch_tok, tree_identifier *id, tree_statement_list *cc, const token& end_tok)
+    : m_try_tok (try_tok), m_try_code (tc), m_catch_tok (catch_tok), m_expr_id (id), m_catch_code (cc), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_try_catch_command)
 
   ~tree_try_catch_command ();
 
+  filepos beg_pos () const { return m_try_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_identifier * identifier () { return m_expr_id; }
 
   tree_statement_list * body () { return m_try_code; }
@@ -86,14 +89,17 @@
 {
 public:
 
-  tree_unwind_protect_command (const token& unwind_tok, tree_statement_list *tc, const token& cleanup_tok, tree_statement_list *cc, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_unwind_tok (unwind_tok), m_unwind_protect_code (tc), m_cleanup_tok (cleanup_tok), m_cleanup_code (cc), m_end_tok (end_tok)
+  tree_unwind_protect_command (const token& unwind_tok, tree_statement_list *tc, const token& cleanup_tok, tree_statement_list *cc, const token& end_tok)
+    : m_unwind_tok (unwind_tok), m_unwind_protect_code (tc), m_cleanup_tok (cleanup_tok), m_cleanup_code (cc), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_unwind_protect_command)
 
   ~tree_unwind_protect_command ();
 
+  filepos beg_pos () const { return m_unwind_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_statement_list * body () { return m_unwind_protect_code; }
 
   tree_statement_list * cleanup () { return m_cleanup_code; }
--- a/libinterp/parse-tree/pt-exp.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-exp.h	Thu Apr 04 01:00:08 2024 -0400
@@ -49,8 +49,9 @@
 {
 public:
 
-  tree_expression (int l = -1, int c = -1)
-    : tree (l, c), m_postfix_index_type ('\0'), m_for_cmd_expr (false), m_print_flag (false) { }
+  tree_expression ()
+    : m_postfix_index_type ('\0'), m_for_cmd_expr (false), m_print_flag (false)
+  { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_expression)
 
--- a/libinterp/parse-tree/pt-fcn-handle.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-fcn-handle.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -53,7 +53,7 @@
 tree_expression *
 tree_fcn_handle::dup (symbol_scope&) const
 {
-  tree_fcn_handle *new_fh = new tree_fcn_handle (m_name, line (), column ());
+  tree_fcn_handle *new_fh = new tree_fcn_handle (m_token);
 
   new_fh->copy_base (*this);
 
@@ -85,10 +85,10 @@
 
   // FIXME: if new scope is nullptr, then we are in big trouble here...
 
-  tree_anon_fcn_handle *new_afh = new
-  tree_anon_fcn_handle (param_list ? param_list->dup (new_scope) : nullptr,
-                        expr ? expr->dup (new_scope) : nullptr,
-                        new_scope, af_parent_scope, line (), column ());
+  tree_anon_fcn_handle *new_afh
+    = new tree_anon_fcn_handle (m_at_tok, param_list ? param_list->dup (new_scope) : nullptr,
+                                expr ? expr->dup (new_scope) : nullptr,
+                                new_scope, af_parent_scope);
 
   new_afh->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-fcn-handle.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-fcn-handle.h	Thu Apr 04 01:00:08 2024 -0400
@@ -49,16 +49,15 @@
 {
 public:
 
-  tree_fcn_handle (int l = -1, int c = -1)
-    : tree_expression (l, c), m_name () { }
-
-  tree_fcn_handle (const std::string& n, int l = -1, int c = -1)
-    : tree_expression (l, c), m_name (n) { }
+  tree_fcn_handle (const token& tok) : m_token (tok), m_name (m_token.text ()) { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_fcn_handle)
 
   ~tree_fcn_handle () = default;
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
   void print (std::ostream& os, bool pr_as_read_syntax = false,
               bool pr_orig_txt = true);
 
@@ -85,6 +84,8 @@
 
 private:
 
+  token m_token;
+
   // The name of this function handle.
   std::string m_name;
 };
@@ -93,24 +94,17 @@
 {
 public:
 
-  tree_anon_fcn_handle (int l = -1, int c = -1)
-    : tree_expression (l, c), m_parameter_list (nullptr),
-      m_expression (nullptr), m_scope (symbol_scope::anonymous ()),
-      m_parent_scope (symbol_scope::invalid ()), m_file_name ()
-  { }
-
-  tree_anon_fcn_handle (tree_parameter_list *pl, tree_expression *ex,
-                        const symbol_scope& scope,
-                        const symbol_scope& parent_scope,
-                        int l = -1, int c = -1)
-    : tree_expression (l, c), m_parameter_list (pl), m_expression (ex),
-      m_scope (scope), m_parent_scope (parent_scope), m_file_name ()
+  tree_anon_fcn_handle (const token& at_tok, tree_parameter_list *pl, tree_expression *ex, const symbol_scope& scope, const symbol_scope& parent_scope)
+    : m_at_tok (at_tok), m_parameter_list (pl), m_expression (ex), m_scope (scope), m_parent_scope (parent_scope)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_anon_fcn_handle)
 
   ~tree_anon_fcn_handle ();
 
+  filepos beg_pos () const { return m_at_tok.beg_pos (); }
+  filepos end_pos () const { return m_expression->end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   tree_parameter_list * parameter_list () const
@@ -143,6 +137,8 @@
 
 private:
 
+  token m_at_tok;
+
   // Inputs parameters.
   tree_parameter_list *m_parameter_list;
 
--- a/libinterp/parse-tree/pt-id.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-id.h	Thu Apr 04 01:00:08 2024 -0400
@@ -55,13 +55,10 @@
 
 public:
 
-  tree_identifier (const token& tok)
-    : tree_expression (tok.line (), tok.column ()), m_sym (), m_token (tok)
-  { }
+  tree_identifier (const token& tok) : m_token (tok) { }
 
   tree_identifier (symbol_scope& scope, const token& tok)
-    : tree_expression (tok.line (), tok.column ()),
-      m_sym (scope ? scope.insert (tok.text ()) : symbol_record (tok.text ())),
+    : m_sym (scope ? scope.insert (tok.text ()) : symbol_record (tok.text ())),
       m_token (tok)
   { }
 
@@ -75,6 +72,9 @@
 
   comment_list leading_comments () const { return m_token.leading_comments (); }
 
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
   virtual bool is_black_hole () const { return false; }
 
   void mark_as_formal_parameter () { m_sym.mark_formal (); }
@@ -123,7 +123,7 @@
 protected:
 
   tree_identifier (symbol_record& sym, const token& tok)
-    : tree_expression (tok.line (), tok.column ()), m_sym (sym), m_token (tok)
+    : m_sym (sym), m_token (tok)
   { }
 
   // The symbol record that this identifier references.
--- a/libinterp/parse-tree/pt-idx.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-idx.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -46,35 +46,31 @@
 
 // Index expressions.
 
-tree_index_expression::tree_index_expression (int l, int c)
-  : tree_expression (l, c)
-{ }
-
-tree_index_expression::tree_index_expression (tree_expression *e, const token& open_delim, tree_argument_list *lst, const token& close_delim, int l, int c, char t)
-  : tree_expression (l, c), m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
+tree_index_expression::tree_index_expression (tree_expression *e, const token& open_delim, tree_argument_list *lst, const token& close_delim, char t)
+  : m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
 {
   append (open_delim, lst, close_delim, t);
 }
 
-tree_index_expression::tree_index_expression (tree_expression *e, const token& dot_tok, const token& struct_elt_tok, int l, int c)
-  : tree_expression (l, c), m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
+tree_index_expression::tree_index_expression (tree_expression *e, const token& dot_tok, const token& struct_elt_tok)
+  : m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
 {
   append (dot_tok, struct_elt_tok);
 }
 
-tree_index_expression::tree_index_expression (tree_expression *e, const token& dot_tok, const token& open_paren, tree_expression *df, const token& close_paren, int l, int c)
-  : tree_expression (l, c), m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
+tree_index_expression::tree_index_expression (tree_expression *e, const token& dot_tok, const token& open_paren, tree_expression *df, const token& close_paren)
+  : m_expr (e), m_args (0), m_type (), m_arg_nm (), m_dyn_field (), m_word_list_cmd (false)
 {
   append (dot_tok, open_paren, df, close_paren);
 }
 
-// FIXME: Need to handle open_delim and close_delim.
-
 tree_index_expression *
-tree_index_expression::append (const token& /*open_delim*/, tree_argument_list *lst, const token& /*close_delim*/, char t)
+tree_index_expression::append (const token& open_delim, tree_argument_list *lst, const token& close_delim, char t)
 {
+  lst->mark_in_delims (open_delim, close_delim);
   m_args.push_back (lst);
   m_type.append (1, t);
+  m_dot_tok.push_back (token ());
   m_arg_nm.push_back (lst ? lst->get_arg_names () : string_vector ());
   m_dyn_field.push_back (static_cast<tree_expression *> (nullptr));
 
@@ -84,27 +80,26 @@
   return this;
 }
 
-// FIXME: Need to handle dot_tok.
-
 tree_index_expression *
-tree_index_expression::append (const token& /*dot_tok*/, const token& struct_elt_tok)
+tree_index_expression::append (const token& dot_tok, const token& struct_elt_tok)
 {
   m_args.push_back (static_cast<tree_argument_list *> (nullptr));
   m_type += '.';
+  m_dot_tok.push_back (dot_tok);
   m_arg_nm.push_back (struct_elt_tok.text ());
   m_dyn_field.push_back (static_cast<tree_expression *> (nullptr));
 
   return this;
 }
 
-// FIXME: Need to handle dot_tok, open_paren, and close_paren.
-
 tree_index_expression *
-tree_index_expression::append (const token& /*dot_tok*/, const token& /*open_paren*/, tree_expression *df, const token& /*close_paren*/)
+tree_index_expression::append (const token& dot_tok, const token& open_paren, tree_expression *df, const token& close_paren)
 {
   m_args.push_back (static_cast<tree_argument_list *> (nullptr));
   m_type += '.';
+  m_dot_tok.push_back (dot_tok);
   m_arg_nm.push_back ("");
+  df->mark_in_delims (open_paren, close_paren);
   m_dyn_field.push_back (df);
 
   return this;
@@ -138,6 +133,31 @@
   return m_expr->name ();
 }
 
+filepos
+tree_index_expression::end_pos () const
+{
+  int n = m_args.size ();
+
+  if (n == 0)
+    return m_expr->end_pos ();
+
+  char idx_type = m_type[n-1];
+
+  if (idx_type == '(' || idx_type == '{')
+    {
+      tree_argument_list *args = m_args.back ();
+      return args->end_pos ();
+    }
+
+  if (idx_type == '.')
+    {
+      tree_expression *dyn_field = m_dyn_field.back ();
+      return dyn_field->end_pos ();
+    }
+
+  panic_impossible ();
+}
+
 std::string
 tree_index_expression::get_struct_index
 (tree_evaluator& tw,
@@ -239,8 +259,7 @@
 tree_index_expression *
 tree_index_expression::dup (symbol_scope& scope) const
 {
-  tree_index_expression *new_idx_expr
-    = new tree_index_expression (line (), column ());
+  tree_index_expression *new_idx_expr = new tree_index_expression ();
 
   new_idx_expr->m_expr = (m_expr ? m_expr->dup (scope) : nullptr);
 
--- a/libinterp/parse-tree/pt-idx.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-idx.h	Thu Apr 04 01:00:08 2024 -0400
@@ -52,11 +52,11 @@
 {
 public:
 
-  tree_index_expression (tree_expression *e, const token& open_delim, tree_argument_list *lst, const token& close_delim, int l, int c, char t);
+  tree_index_expression (tree_expression *e, const token& open_delim, tree_argument_list *lst, const token& close_delim, char t);
 
-  tree_index_expression (tree_expression *e, const token& dot_tok, const token& struct_elt_tok, int l = -1, int c = -1);
+  tree_index_expression (tree_expression *e, const token& dot_tok, const token& struct_elt_tok);
 
-  tree_index_expression (tree_expression *e, const token& dot_tok, const token& open_paren, tree_expression *df, const token& close_paren, int l = -1, int c = -1);
+  tree_index_expression (tree_expression *e, const token& dot_tok, const token& open_paren, tree_expression *df, const token& close_paren);
 
   OCTAVE_DISABLE_COPY_MOVE (tree_index_expression)
 
@@ -73,6 +73,9 @@
 
   std::string name () const;
 
+  filepos beg_pos () const { return m_expr->beg_pos (); }
+  filepos end_pos () const;
+
   tree_expression * expression () { return m_expr; }
 
   std::list<tree_argument_list *> arg_lists () { return m_args; }
@@ -119,12 +122,18 @@
   // The LHS of this index expression.
   tree_expression *m_expr {nullptr};
 
+  // FIXME: maybe all the things in the list should be in a struct or
+  // class so we can more easily ensure that they remain synchronized.
+
   // The indices (only valid if type == paren || type == brace).
   std::list<tree_argument_list *> m_args;
 
   // The type of this index expression.
   std::string m_type;
 
+  // Record dot tokens for position and possible comment info.
+  std::list<token> m_dot_tok;
+
   // The names of the arguments.  Used for constant struct element
   // references.
   std::list<string_vector> m_arg_nm;
@@ -135,7 +144,7 @@
   // TRUE if this expression was parsed as a word list command.
   bool m_word_list_cmd {false};
 
-  tree_index_expression (int l, int c);
+  tree_index_expression () = default;
 
   octave_map make_arg_struct () const;
 };
--- a/libinterp/parse-tree/pt-jump.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-jump.h	Thu Apr 04 01:00:08 2024 -0400
@@ -33,14 +33,33 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
-// Break.
+// Base class for jump commands
 
-class tree_break_command : public tree_command
+class tree_jump_command : public tree_command
 {
 public:
 
-  tree_break_command (int l = -1, int c = -1)
-    : tree_command (l, c) { }
+  tree_jump_command (const token& tok) : m_token (tok) { }
+
+  OCTAVE_DISABLE_COPY_MOVE (tree_jump_command)
+
+  ~tree_jump_command () = default;
+
+  filepos beg_pos () const { return m_token.beg_pos (); }
+  filepos end_pos () const { return m_token.end_pos (); }
+
+protected:
+
+  token m_token;
+};
+
+// Break.
+
+class tree_break_command : public tree_jump_command
+{
+public:
+
+  tree_break_command (const token& tok) : tree_jump_command (tok) { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_break_command)
 
@@ -54,12 +73,11 @@
 
 // Continue.
 
-class tree_continue_command : public tree_command
+class tree_continue_command : public tree_jump_command
 {
 public:
 
-  tree_continue_command (int l = -1, int c = -1)
-    : tree_command (l, c) { }
+  tree_continue_command (const token& tok) : tree_jump_command (tok) { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_continue_command)
 
@@ -73,12 +91,11 @@
 
 // Return.
 
-class tree_return_command : public tree_command
+class tree_return_command : public tree_jump_command
 {
 public:
 
-  tree_return_command (int l = -1, int c = -1)
-    : tree_command (l, c) { }
+  tree_return_command (const token& tok) : tree_jump_command (tok) { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_return_command)
 
--- a/libinterp/parse-tree/pt-loop.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-loop.h	Thu Apr 04 01:00:08 2024 -0400
@@ -46,14 +46,17 @@
 {
 public:
 
-  tree_while_command (const token& while_tok, tree_expression *expr, tree_statement_list *body, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_while_tok (while_tok), m_expr (expr), m_body (body), m_end_tok (end_tok)
+  tree_while_command (const token& while_tok, tree_expression *expr, tree_statement_list *body, const token& end_tok)
+    : m_while_tok (while_tok), m_expr (expr), m_body (body), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_while_command)
 
   ~tree_while_command ();
 
+  filepos beg_pos () const { return m_while_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_expression * condition () { return m_expr; }
 
   tree_statement_list * body () { return m_body; }
@@ -82,14 +85,17 @@
 {
 public:
 
-  tree_do_until_command (const token& do_tok, tree_statement_list *body, const token& until_tok, tree_expression *expr, int l = -1, int c = -1)
-    : tree_command (l, c), m_do_tok (do_tok), m_body (body), m_until_tok (until_tok), m_expr (expr)
+  tree_do_until_command (const token& do_tok, tree_statement_list *body, const token& until_tok, tree_expression *expr)
+    : m_do_tok (do_tok), m_body (body), m_until_tok (until_tok), m_expr (expr)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_do_until_command)
 
   ~tree_do_until_command ();
 
+  filepos beg_pos () const { return m_do_tok.beg_pos (); }
+  filepos end_pos () const { return m_expr->end_pos (); }
+
   tree_statement_list * body () { return m_body; }
 
   tree_expression * condition () { return m_expr; }
@@ -120,8 +126,8 @@
 
   tree_simple_for_command (bool parfor, const token& for_tok, const token& open_paren, tree_expression *le, const token& eq_tok,
                            tree_expression *re, const token& sep_tok, tree_expression *maxproc_arg, const token& close_paren,
-                           tree_statement_list *body, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_parfor (parfor), m_for_tok (for_tok), m_open_paren (open_paren), m_lhs (le), m_eq_tok (eq_tok),
+                           tree_statement_list *body, const token& end_tok)
+    : m_parfor (parfor), m_for_tok (for_tok), m_open_paren (open_paren), m_lhs (le), m_eq_tok (eq_tok),
       m_expr (re), m_sep_tok (sep_tok), m_maxproc (maxproc_arg), m_close_paren (close_paren),
       m_body (body), m_end_tok (end_tok)
   { }
@@ -132,6 +138,9 @@
 
   bool in_parallel () { return m_parfor; }
 
+  filepos beg_pos () const { return m_for_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_expression * left_hand_side () { return m_lhs; }
 
   tree_expression * control_expr () { return m_expr; }
@@ -181,15 +190,17 @@
 public:
 
   tree_complex_for_command (const token& for_tok, tree_argument_list *le, const token& eq_tok, tree_expression *re,
-                            tree_statement_list *body, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_for_tok (for_tok), m_lhs (le), m_eq_tok (eq_tok), m_expr (re),
-      m_body (body), m_end_tok (end_tok)
+                            tree_statement_list *body, const token& end_tok)
+    : m_for_tok (for_tok), m_lhs (le), m_eq_tok (eq_tok), m_expr (re), m_body (body), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_complex_for_command)
 
   ~tree_complex_for_command ();
 
+  filepos beg_pos () const { return m_for_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_argument_list * left_hand_side () { return m_lhs; }
 
   tree_expression * control_expr () { return m_expr; }
--- a/libinterp/parse-tree/pt-mat.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-mat.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -141,7 +141,7 @@
 tree_expression *
 tree_matrix::dup (symbol_scope& scope) const
 {
-  tree_matrix *new_matrix = new tree_matrix (nullptr, line (), column ());
+  tree_matrix *new_matrix = new tree_matrix (nullptr);
 
   new_matrix->copy_base (*this, scope);
 
--- a/libinterp/parse-tree/pt-mat.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-mat.h	Thu Apr 04 01:00:08 2024 -0400
@@ -49,8 +49,8 @@
 {
 public:
 
-  tree_matrix (tree_argument_list *row = nullptr, int l = -1, int c = -1)
-    : tree_array_list (row, l, c)
+  tree_matrix (tree_argument_list *row = nullptr)
+    : tree_array_list (row)
   { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_matrix)
--- a/libinterp/parse-tree/pt-misc.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-misc.h	Thu Apr 04 01:00:08 2024 -0400
@@ -81,6 +81,9 @@
     return this;
   }
 
+  filepos beg_pos () const { return m_open_delim.beg_pos (); }
+  filepos end_pos () const { return m_close_delim.end_pos (); }
+
   void mark_as_formal_parameters ();
 
   void mark_varargs () { m_marked_for_varargs = 1; }
--- a/libinterp/parse-tree/pt-select.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-select.h	Thu Apr 04 01:00:08 2024 -0400
@@ -32,6 +32,7 @@
 
 #include "comment-list.h"
 #include "pt-cmd.h"
+#include "pt-stmt.h"
 #include "pt-walk.h"
 #include "token.h"
 
@@ -47,8 +48,8 @@
 {
 public:
 
-  tree_if_clause (const token& tok, tree_expression *e, tree_statement_list *sl, int l = -1, int c = -1)
-    : tree (l, c), m_tok (tok), m_expr (e), m_list (sl)
+  tree_if_clause (const token& tok, tree_expression *e, tree_statement_list *sl)
+    : m_tok (tok), m_expr (e), m_list (sl)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_if_clause)
@@ -59,6 +60,9 @@
 
   bool is_else_clause () { return ! m_expr; }
 
+  filepos beg_pos () const { return m_tok.beg_pos (); }
+  filepos end_pos () const { return m_list->end_pos (); }
+
   tree_expression * condition () { return m_expr; }
 
   tree_statement_list * commands () { return m_list; }
@@ -101,6 +105,24 @@
       }
   }
 
+  filepos beg_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_if_clause *elt = front ();
+    return elt->beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_if_clause *elt = back ();
+    return elt->end_pos ();
+  }
+
   token if_token () const
   {
     if (! empty ())
@@ -122,18 +144,21 @@
 {
 public:
 
-  tree_if_command (const token& if_tok, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_if_tok (if_tok), m_end_tok (end_tok)
+  tree_if_command (const token& if_tok, const token& end_tok)
+    : m_if_tok (if_tok), m_end_tok (end_tok)
   { }
 
-  tree_if_command (const token& if_tok, tree_if_command_list *lst, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_if_tok (if_tok), m_list (lst), m_end_tok (end_tok)
+  tree_if_command (const token& if_tok, tree_if_command_list *lst, const token& end_tok)
+    : m_if_tok (if_tok), m_list (lst), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_if_command)
 
   ~tree_if_command ();
 
+  filepos beg_pos () const { return m_if_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_if_command_list * cmd_list () { return m_list; }
 
   comment_list leading_comments () const { return m_if_tok.leading_comments (); }
@@ -159,12 +184,12 @@
 {
 public:
 
-  tree_switch_case (const token& tok, tree_statement_list *sl, int l = -1, int c = -1)
-    : tree (l, c), m_tok (tok), m_list (sl)
+  tree_switch_case (const token& tok, tree_statement_list *sl)
+    : m_tok (tok), m_list (sl)
   { }
 
-  tree_switch_case (const token& tok, tree_expression *e, tree_statement_list *sl, int l = -1, int c = -1)
-    : tree (l, c), m_tok (tok), m_label (e), m_list (sl)
+  tree_switch_case (const token& tok, tree_expression *e, tree_statement_list *sl)
+    : m_tok (tok), m_label (e), m_list (sl)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_switch_case)
@@ -173,6 +198,9 @@
 
   bool is_default_case () { return ! m_label; }
 
+  filepos beg_pos () const { return m_tok.beg_pos (); }
+  filepos end_pos () const { return m_list->end_pos (); }
+
   tree_expression * case_label () { return m_label; }
 
   tree_statement_list * commands () { return m_list; }
@@ -215,6 +243,24 @@
       }
   }
 
+  filepos beg_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_switch_case *elt = front ();
+    return elt->beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_switch_case *elt = back ();
+    return elt->end_pos ();
+  }
+
   void accept (tree_walker& tw)
   {
     tw.visit_switch_case_list (*this);
@@ -225,14 +271,17 @@
 {
 public:
 
-  tree_switch_command (const token& switch_tok, tree_expression *e, tree_switch_case_list *lst, const token& end_tok, int l = -1, int c = -1)
-    : tree_command (l, c), m_switch_tok (switch_tok), m_expr (e), m_list (lst), m_end_tok (end_tok)
+  tree_switch_command (const token& switch_tok, tree_expression *e, tree_switch_case_list *lst, const token& end_tok)
+    : m_switch_tok (switch_tok), m_expr (e), m_list (lst), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_switch_command)
 
   ~tree_switch_command ();
 
+  filepos beg_pos () const { return m_switch_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_expression * switch_value () { return m_expr; }
 
   tree_switch_case_list * case_list () { return m_list; }
--- a/libinterp/parse-tree/pt-spmd.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-spmd.h	Thu Apr 04 01:00:08 2024 -0400
@@ -42,14 +42,17 @@
 {
 public:
 
-  tree_spmd_command (tree_statement_list *body, int l = -1, int c = -1)
-    : tree_command (l, c), m_body (body)
+  tree_spmd_command (const token& spmd_tok, tree_statement_list *body, const token& end_tok)
+    : m_spmd_tok (spmd_tok), m_body (body), m_end_tok (end_tok)
   { }
 
   OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (tree_spmd_command)
 
   ~tree_spmd_command ();
 
+  filepos beg_pos () const { return m_spmd_tok.beg_pos (); }
+  filepos end_pos () const { return m_end_tok.end_pos (); }
+
   tree_statement_list * body () { return m_body; }
 
   void accept (tree_walker& tw)
@@ -59,8 +62,12 @@
 
 private:
 
+  token m_spmd_tok;
+
   // List of commands.
   tree_statement_list *m_body;
+
+  token m_end_tok;
 };
 
 OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/pt-stmt.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-stmt.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -114,6 +114,27 @@
           : m_expression->leading_comments ());
 }
 
+filepos
+tree_statement::beg_pos () const
+{
+  return (m_command ? m_command->beg_pos () : m_expression->beg_pos ());
+}
+
+filepos
+tree_statement::end_pos () const
+{
+  return (m_command ? m_command->end_pos () : m_expression->end_pos ());
+}
+
+void
+tree_statement::update_end_pos (const filepos& pos)
+{
+  if (m_command)
+    m_command->update_end_pos (pos);
+  else
+    panic_impossible ();
+}
+
 std::string
 tree_statement::bp_cond () const
 {
@@ -139,15 +160,6 @@
 }
 
 void
-tree_statement::set_location (int l, int c)
-{
-  if (m_command)
-    m_command->set_location (l, c);
-  else if (m_expression)
-    m_expression->set_location (l, c);
-}
-
-void
 tree_statement::echo_code (const std::string& prefix)
 {
   tree_print_code tpc (octave_stdout, prefix);
--- a/libinterp/parse-tree/pt-stmt.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-stmt.h	Thu Apr 04 01:00:08 2024 -0400
@@ -87,13 +87,16 @@
 
   comment_list leading_comments () const;
 
+  filepos beg_pos () const;
+  filepos end_pos () const;
+
+  virtual void update_end_pos (const filepos& pos);
+
   std::string bp_cond () const;
 
   int line () const;
   int column () const;
 
-  void set_location (int l, int c);
-
   void echo_code (const std::string& prefix);
 
   tree_command * command () { return m_command; }
@@ -159,6 +162,24 @@
       }
   }
 
+  filepos beg_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_statement *elt = front ();
+    return elt->beg_pos ();
+  }
+
+  filepos end_pos () const
+  {
+    if (empty ())
+      return filepos ();
+
+    tree_statement *elt = back ();
+    return elt->end_pos ();
+  }
+
   void mark_as_function_body () { m_function_body = true; }
 
   void mark_as_anon_function_body () { m_anon_function_body = true; }
--- a/libinterp/parse-tree/pt-unop.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-unop.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -48,8 +48,7 @@
 tree_prefix_expression::dup (symbol_scope& scope) const
 {
   tree_prefix_expression *new_pe
-    = new tree_prefix_expression (m_op ? m_op->dup (scope) : nullptr,
-                                  line (), column (), m_etype);
+    = new tree_prefix_expression (m_op_tok, m_op ? m_op->dup (scope) : nullptr, m_etype);
 
   new_pe->copy_base (*this);
 
@@ -110,8 +109,7 @@
 tree_postfix_expression::dup (symbol_scope& scope) const
 {
   tree_postfix_expression *new_pe
-    = new tree_postfix_expression (m_op ? m_op->dup (scope) : nullptr,
-                                   line (), column (), m_etype);
+    = new tree_postfix_expression (m_op ? m_op->dup (scope) : nullptr, m_op_tok, m_etype);
 
   new_pe->copy_base (*this);
 
--- a/libinterp/parse-tree/pt-unop.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt-unop.h	Thu Apr 04 01:00:08 2024 -0400
@@ -36,6 +36,7 @@
 #include "ov.h"
 #include "pt-exp.h"
 #include "pt-walk.h"
+#include "token.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
@@ -47,15 +48,12 @@
 {
 protected:
 
-  tree_unary_expression (int l = -1, int c = -1,
-                         octave_value::unary_op t
-                         = octave_value::unknown_unary_op)
-    : tree_expression (l, c), m_op (nullptr), m_etype (t)  { }
+  tree_unary_expression (octave_value::unary_op t = octave_value::unknown_unary_op)
+    : m_op (nullptr), m_etype (t)  { }
 
-  tree_unary_expression (tree_expression *e, int l = -1, int c = -1,
-                         octave_value::unary_op t
-                         = octave_value::unknown_unary_op)
-    : tree_expression (l, c), m_op (e), m_etype (t) { }
+  tree_unary_expression (const token& op_tok, tree_expression *e, octave_value::unary_op t = octave_value::unknown_unary_op)
+    : m_op_tok (op_tok), m_op (e), m_etype (t)
+  { }
 
 public:
 
@@ -73,6 +71,9 @@
 
 protected:
 
+  // The operator token.
+  token m_op_tok;
+
   // The operand for the expression.
   tree_expression *m_op;
 
@@ -86,18 +87,17 @@
 {
 public:
 
-  tree_prefix_expression (int l = -1, int c = -1)
-    : tree_unary_expression (l, c, octave_value::unknown_unary_op) { }
-
-  tree_prefix_expression (tree_expression *e, int l = -1, int c = -1,
-                          octave_value::unary_op t
-                          = octave_value::unknown_unary_op)
-    : tree_unary_expression (e, l, c, t) { }
+  tree_prefix_expression (const token& op_tok, tree_expression *e, octave_value::unary_op t = octave_value::unknown_unary_op)
+    : tree_unary_expression (op_tok, e, t)
+  { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_prefix_expression)
 
   ~tree_prefix_expression () = default;
 
+  filepos beg_pos () const { return m_op_tok.beg_pos (); }
+  filepos end_pos () const { return m_op->end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   tree_expression * dup (symbol_scope& scope) const;
@@ -123,18 +123,17 @@
 {
 public:
 
-  tree_postfix_expression (int l = -1, int c = -1)
-    : tree_unary_expression (l, c, octave_value::unknown_unary_op) { }
-
-  tree_postfix_expression (tree_expression *e, int l = -1, int c = -1,
-                           octave_value::unary_op t
-                           = octave_value::unknown_unary_op)
-    : tree_unary_expression (e, l, c, t) { }
+  tree_postfix_expression (tree_expression *e, const token& op_tok, octave_value::unary_op t = octave_value::unknown_unary_op)
+    : tree_unary_expression (op_tok, e, t)
+  { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree_postfix_expression)
 
   ~tree_postfix_expression () = default;
 
+  filepos beg_pos () const { return m_op->beg_pos (); }
+  filepos end_pos () const { return m_op_tok.end_pos (); }
+
   bool rvalue_ok () const { return true; }
 
   tree_expression * dup (symbol_scope& scope) const;
--- a/libinterp/parse-tree/pt.cc	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt.cc	Thu Apr 04 01:00:08 2024 -0400
@@ -31,6 +31,7 @@
 #include <string>
 
 #include "comment-list.h"
+#include "filepos.h"
 #include "interpreter.h"
 #include "ov-fcn.h"
 #include "pt.h"
@@ -40,6 +41,18 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
+int
+tree::line () const
+{
+  return beg_pos().line ();
+}
+
+int
+tree::column () const
+{
+  return beg_pos().column ();
+}
+
 comment_list
 tree::leading_comments () const
 {
--- a/libinterp/parse-tree/pt.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/pt.h	Thu Apr 04 01:00:08 2024 -0400
@@ -37,6 +37,7 @@
 OCTAVE_BEGIN_NAMESPACE(octave)
 
 class comment_list;
+class filepos;
 class tree_evaluator;
 class tree_walker;
 
@@ -46,27 +47,17 @@
 {
 public:
 
-  tree (int l = -1, int c = -1)
-    : m_line_num (l), m_column_num (c), m_bp_cond (nullptr)
-  { }
+  tree () : m_bp_cond (nullptr) { }
 
   OCTAVE_DISABLE_COPY_MOVE (tree)
 
   virtual ~tree () = default;
 
-  virtual int line () const { return m_line_num; }
-
-  virtual int column () const { return m_column_num; }
-
-  void line (int l) { m_line_num = l; }
+  virtual int line () const;
+  virtual int column () const;
 
-  void column (int c) { m_column_num = c; }
-
-  void set_location (int l, int c)
-  {
-    m_line_num = l;
-    m_column_num = c;
-  }
+  virtual filepos beg_pos () const = 0;
+  virtual filepos end_pos () const = 0;
 
   // FIXME: maybe make this a pure virtual function?
   virtual comment_list leading_comments () const;
@@ -111,11 +102,6 @@
 
 private:
 
-  // The input line and column where we found the text that was
-  // eventually converted to this tree node.
-  int m_line_num;
-  int m_column_num;
-
   // NULL if no breakpoint, or a breakpoint condition if there is one.
   std::string *m_bp_cond;
 };
--- a/libinterp/parse-tree/token.h	Wed Apr 03 12:30:26 2024 -0400
+++ b/libinterp/parse-tree/token.h	Thu Apr 04 01:00:08 2024 -0400
@@ -115,6 +115,8 @@
   void mark_trailing_space () { m_tspc = true; }
   bool space_follows_token () const { return m_tspc; }
 
+  operator bool () const { return m_type_tag != invalid_token; }
+
   int token_id () const { return m_tok_id; }
 
   bool token_is (int id) const { return m_tok_id == id; }