view libinterp/parse-tree/oct-parse.yy @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents a2397a85d7e5
children c9788d7f6e65 4dbd8d999b9e
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1993-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

// Parser for Octave.

// C decarations.

%{

#define YYDEBUG 1

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <cassert>
#include <cstdio>
#include <cstdlib>

#include <iostream>
#include <map>
#include <sstream>

#include "Matrix.h"
#include "cmd-edit.h"
#include "cmd-hist.h"
#include "file-ops.h"
#include "file-stat.h"
#include "oct-env.h"
#include "oct-time.h"
#include "quit.h"

#include "Cell.h"
#include "anon-fcn-validator.h"
#include "builtin-defun-decls.h"
#include "defun.h"
#include "dynamic-ld.h"
#include "error.h"
#include "input.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "lex.h"
#include "load-path.h"
#include "lo-sysdep.h"
#include "oct-hist.h"
#include "oct-map.h"
#include "ov-classdef.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "ov-null-mat.h"
#include "pager.h"
#include "parse.h"
#include "pt-all.h"
#include "pt-eval.h"
#include "symtab.h"
#include "token.h"
#include "unwind-prot.h"
#include "utils.h"
#include "variables.h"

// oct-parse.h must be included after pt-all.h
#include "oct-parse.h"

extern int octave_lex (YYSTYPE *, void *);

// Forward declarations for some functions defined at the bottom of
// the file.

static void yyerror (octave::base_parser& parser, const char *s);

#define lexer (parser.get_lexer ())
#define scanner lexer.m_scanner

// Previous versions of Octave used Bison's YYUSE macro to avoid
// warnings about unused values in rules.  But that Bison macro was
// apparently never intended to be public.  So define our own.  All we
// need to do is mention the symantic value somewhere in the rule.  It
// doesn't actually need to be used to avoid the Bison warning, so just
// define this macro to discard its parameter.
#define OCTAVE_YYUSE(X, ...)

#if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
   // Disable this warning for code that is generated by Bison,
   // including grammar rules.  Push the current state so we can
   // restore the warning state prior to functions we define at
   // the bottom of the file.
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wold-style-cast"
#endif

%}

// Bison declarations.

// The grammar currently has 9 shift/reduce conflicts.  Ensure that
// we notice if that number changes.

%expect 9

// We are using the pure parser interface and the reentrant lexer
// interface but the Octave parser and lexer are NOT properly
// reentrant because both still use many global variables.  It should be
// safe to create a parser object and call it while another parser
// object is active (to parse a callback function while the main
// interactive parser is waiting for input, for example) if you take
// care to properly save and restore (typically with an unwind_protect
// object) relevant global values before and after the nested call.

%define api.pure
// No spaces inside the braces for the prefix and push-pull definitions!
%define api.prefix {octave_}
%define api.push-pull both
%parse-param { octave::base_parser& parser }
%lex-param { void *lexer.scanner }

%union
{
  int dummy_type;

  // The type of the basic tokens returned by the lexer.
  octave::token *tok_val;

  // Comment strings that we need to deal with mid-rule.
  octave::comment_list *comment_type;

  // Types for the nonterminals we generate.
  char punct_type;
  octave::tree *tree_type;
  octave::tree_matrix *tree_matrix_type;
  octave::tree_cell *tree_cell_type;
  octave::tree_expression *tree_expression_type;
  octave::tree_constant *tree_constant_type;
  octave::tree_fcn_handle *tree_fcn_handle_type;
  octave::tree_superclass_ref *tree_superclass_ref_type;
  octave::tree_metaclass_query *tree_metaclass_query_type;
  octave::tree_function_def *tree_function_def_type;
  octave::tree_anon_fcn_handle *tree_anon_fcn_handle_type;
  octave::tree_identifier *tree_identifier_type;
  octave::tree_index_expression *tree_index_expression_type;
  octave::tree_colon_expression *tree_colon_expression_type;
  octave::tree_argument_list *tree_argument_list_type;
  octave::tree_parameter_list *tree_parameter_list_type;
  octave::tree_command *tree_command_type;
  octave::tree_if_command *tree_if_command_type;
  octave::tree_if_clause *tree_if_clause_type;
  octave::tree_if_command_list *tree_if_command_list_type;
  octave::tree_switch_command *tree_switch_command_type;
  octave::tree_switch_case *tree_switch_case_type;
  octave::tree_switch_case_list *tree_switch_case_list_type;
  octave::tree_decl_elt *tree_decl_elt_type;
  octave::tree_decl_init_list *tree_decl_init_list_type;
  octave::tree_decl_command *tree_decl_command_type;
  octave::tree_statement *tree_statement_type;
  octave::tree_statement_list *tree_statement_list_type;
  octave::tree_arguments_block *tree_arguments_block_type;
  octave::tree_args_block_attribute_list *tree_args_block_attribute_list_type;
  octave::tree_args_block_validation_list *tree_args_block_validation_list_type;
  octave::tree_arg_size_spec *tree_arg_size_spec_type;
  octave::tree_arg_validation *tree_arg_validation_type;
  octave::tree_arg_validation_fcns *tree_arg_validation_fcns_type;
  octave_user_function *octave_user_function_type;

  octave::tree_classdef *tree_classdef_type;
  octave::tree_classdef_attribute* tree_classdef_attribute_type;
  octave::tree_classdef_attribute_list* tree_classdef_attribute_list_type;
  octave::tree_classdef_superclass* tree_classdef_superclass_type;
  octave::tree_classdef_superclass_list* tree_classdef_superclass_list_type;
  octave::tree_classdef_body* tree_classdef_body_type;
  octave::tree_classdef_property* tree_classdef_property_type;
  octave::tree_classdef_property_list* tree_classdef_property_list_type;
  octave::tree_classdef_properties_block* tree_classdef_properties_block_type;
  octave::tree_classdef_methods_list* tree_classdef_methods_list_type;
  octave::tree_classdef_methods_block* tree_classdef_methods_block_type;
  octave::tree_classdef_event* tree_classdef_event_type;
  octave::tree_classdef_events_list* tree_classdef_events_list_type;
  octave::tree_classdef_events_block* tree_classdef_events_block_type;
  octave::tree_classdef_enum* tree_classdef_enum_type;
  octave::tree_classdef_enum_list* tree_classdef_enum_list_type;
  octave::tree_classdef_enum_block* tree_classdef_enum_block_type;
}

// Tokens with line and column information.
%token <tok_val> '=' ':' '-' '+' '*' '/'
%token <tok_val> '(' ')' '[' ']' '{' '}' '.' '@'
%token <tok_val> ',' ';' '\n'
%token <tok_val> ADD_EQ SUB_EQ MUL_EQ DIV_EQ LEFTDIV_EQ POW_EQ
%token <tok_val> EMUL_EQ EDIV_EQ ELEFTDIV_EQ EPOW_EQ AND_EQ OR_EQ
%token <tok_val> EXPR_AND_AND EXPR_OR_OR
%token <tok_val> EXPR_AND EXPR_OR EXPR_NOT
%token <tok_val> EXPR_LT EXPR_LE EXPR_EQ EXPR_NE EXPR_GE EXPR_GT
%token <tok_val> LEFTDIV EMUL EDIV ELEFTDIV
%token <tok_val> HERMITIAN TRANSPOSE
%token <tok_val> PLUS_PLUS MINUS_MINUS POW EPOW
%token <tok_val> NUMBER
%token <tok_val> STRUCT_ELT
%token <tok_val> NAME
%token <tok_val> END
%token <tok_val> DQ_STRING SQ_STRING
%token <tok_val> FOR PARFOR WHILE DO UNTIL
%token <tok_val> SPMD
%token <tok_val> IF ELSEIF ELSE
%token <tok_val> SWITCH CASE OTHERWISE
%token <tok_val> BREAK CONTINUE FUNC_RET
%token <tok_val> UNWIND CLEANUP
%token <tok_val> TRY CATCH
%token <tok_val> GLOBAL PERSISTENT
%token <tok_val> FCN_HANDLE
%token <tok_val> CLASSDEF
%token <tok_val> PROPERTIES METHODS EVENTS ENUMERATION
%token <tok_val> METAQUERY
%token <tok_val> SUPERCLASSREF
%token <tok_val> FQ_IDENT
%token <tok_val> GET SET
%token <tok_val> FCN
%token <tok_val> ARGUMENTS
%token <tok_val> LEXICAL_ERROR
%token <tok_val> END_OF_INPUT

// Other tokens.
%token <dummy_type> INPUT_FILE
// %token VARARGIN VARARGOUT

// Nonterminals we construct.
%type <dummy_type> indirect_ref_op
%type <dummy_type> push_fcn_symtab push_script_symtab begin_file
%type <dummy_type> param_list_beg param_list_end stmt_begin anon_fcn_begin
%type <dummy_type> parsing_local_fcns parse_error at_first_executable_stmt
%type <comment_type> stash_comment
%type <tok_val> function_beg classdef_beg arguments_beg
%type <tok_val> properties_beg methods_beg events_beg enumeration_beg
%type <punct_type> sep_no_nl opt_sep_no_nl nl opt_nl sep opt_sep
%type <tree_type> input
%type <tree_constant_type> string constant magic_colon
%type <tree_anon_fcn_handle_type> anon_fcn_handle
%type <tree_fcn_handle_type> fcn_handle
%type <tree_matrix_type> matrix_rows
%type <tree_cell_type> cell_rows
%type <tree_expression_type> matrix cell
%type <tree_expression_type> primary_expr oper_expr power_expr
%type <tree_expression_type> simple_expr colon_expr assign_expr expression
%type <tree_expression_type> arg_name
%type <tree_identifier_type> identifier fcn_name magic_tilde
%type <tree_superclass_ref_type> superclass_identifier
%type <tree_metaclass_query_type> meta_identifier
%type <tree_index_expression_type> word_list_cmd
%type <tree_argument_list_type> arg_list word_list assign_lhs
%type <tree_argument_list_type> cell_or_matrix_row
%type <tree_parameter_list_type> opt_param_list param_list
%type <tree_parameter_list_type> param_list1 param_list2
%type <tree_parameter_list_type> return_list return_list1
%type <tree_command_type> command select_command loop_command
%type <tree_command_type> jump_command spmd_command except_command
%type <tree_function_def_type> function
%type <tree_classdef_type> classdef
%type <tree_command_type> file
%type <tree_if_command_type> if_command
%type <tree_if_clause_type> elseif_clause else_clause
%type <tree_if_command_list_type> if_cmd_list1 if_cmd_list
%type <tree_switch_command_type> switch_command
%type <tree_switch_case_type> switch_case default_case
%type <tree_switch_case_list_type> case_list1 case_list
%type <tree_decl_elt_type> decl_elt param_list_elt
%type <tree_decl_init_list_type> decl_init_list
%type <tree_decl_command_type> declaration
%type <tree_statement_type> statement function_end
%type <tree_statement_list_type> simple_list simple_list1 list list1
%type <tree_statement_list_type> opt_list function_body function_body1
%type <tree_statement_list_type> opt_fcn_list fcn_list fcn_list1
%type <tree_classdef_attribute_type> attr
%type <tree_classdef_attribute_list_type> attr_list attr_list1
%type <tree_classdef_superclass_type> superclass
%type <tree_classdef_superclass_list_type> superclass_list superclass_list1
%type <tree_classdef_body_type> class_body class_body1
%type <tree_classdef_property_type> class_property
%type <tree_classdef_property_list_type> property_list property_list1
%type <tree_classdef_properties_block_type> properties_block
%type <tree_classdef_methods_list_type> methods_list methods_list1
%type <tree_classdef_methods_block_type> methods_block
%type <tree_classdef_event_type> class_event
%type <tree_classdef_events_list_type> events_list events_list1
%type <tree_classdef_events_block_type> events_block
%type <tree_classdef_enum_type> class_enum
%type <tree_classdef_enum_list_type> enum_list enum_list1
%type <tree_classdef_enum_block_type> enum_block
%type <tree_function_def_type> method_decl method
%type <tree_arguments_block_type> arguments_block
%type <tree_args_block_attribute_list_type> args_attr_list
%type <tree_args_block_validation_list_type> args_validation_list
%type <tree_arg_validation_type> arg_validation
%type <tree_arg_size_spec_type> size_spec
%type <tree_identifier_type> class_name
%type <tree_arg_validation_fcns_type> validation_fcns
%type <tree_expression_type> default_value
%type <octave_user_function_type> method_decl1

// Precedence and associativity.
%right '=' ADD_EQ SUB_EQ MUL_EQ DIV_EQ LEFTDIV_EQ POW_EQ EMUL_EQ EDIV_EQ ELEFTDIV_EQ EPOW_EQ OR_EQ AND_EQ
%left EXPR_OR_OR
%left EXPR_AND_AND
%left EXPR_OR
%left EXPR_AND
%left EXPR_LT EXPR_LE EXPR_EQ EXPR_NE EXPR_GE EXPR_GT
%left ':'
%left '-' '+'
%left '*' '/' LEFTDIV EMUL EDIV ELEFTDIV
%right UNARY EXPR_NOT
%left POW EPOW HERMITIAN TRANSPOSE
%right PLUS_PLUS MINUS_MINUS
%left '(' '.' '{'

// How to clean up if there is a parse error.  We handle deleting tokens
// and comments separately and separators are just characters.  The
// remaining items are dynamically allocated parse tree objects that
// must be deleted.  Use the wildcard case (<*>) to detect unhandled
// cases (for example, a new semantic type is added but not handled
// here).

%destructor { } <tok_val>
%destructor { } <punct_type>
%destructor { } <comment_type>
%destructor { } <>

%destructor { delete $$; } <tree_type>
%destructor { delete $$; } <tree_matrix_type>
%destructor { delete $$; } <tree_cell_type>
%destructor { delete $$; } <tree_expression_type>
%destructor { delete $$; } <tree_constant_type>
%destructor { delete $$; } <tree_fcn_handle_type>
%destructor { delete $$; } <tree_superclass_ref_type>
%destructor { delete $$; } <tree_metaclass_query_type>
%destructor { delete $$; } <tree_function_def_type>
%destructor { delete $$; } <tree_anon_fcn_handle_type>
%destructor { delete $$; } <tree_identifier_type>
%destructor { delete $$; } <tree_index_expression_type>
%destructor { delete $$; } <tree_argument_list_type>
%destructor { delete $$; } <tree_parameter_list_type>
%destructor { delete $$; } <tree_command_type>
%destructor { delete $$; } <tree_if_command_type>
%destructor { delete $$; } <tree_if_clause_type>
%destructor { delete $$; } <tree_if_command_list_type>
%destructor { delete $$; } <tree_switch_command_type>
%destructor { delete $$; } <tree_switch_case_type>
%destructor { delete $$; } <tree_switch_case_list_type>
%destructor { delete $$; } <tree_decl_elt_type>
%destructor { delete $$; } <tree_decl_init_list_type>
%destructor { delete $$; } <tree_decl_command_type>
%destructor { delete $$; } <tree_statement_type>
%destructor { delete $$; } <tree_statement_list_type>
%destructor { delete $$; } <tree_arguments_block_type>
%destructor { delete $$; } <tree_args_block_attribute_list_type>
%destructor { delete $$; } <tree_args_block_validation_list_type>
%destructor { delete $$; } <tree_arg_validation_type>
%destructor { delete $$; } <tree_arg_size_spec_type>
%destructor { delete $$; } <tree_arg_validation_fcns_type>
%destructor { delete $$; } <octave_user_function_type>

%destructor { delete $$; } <tree_classdef_type>
%destructor { delete $$; } <tree_classdef_attribute_type>
%destructor { delete $$; } <tree_classdef_attribute_list_type>
%destructor { delete $$; } <tree_classdef_superclass_type>
%destructor { delete $$; } <tree_classdef_superclass_list_type>
%destructor { delete $$; } <tree_classdef_body_type>
%destructor { delete $$; } <tree_classdef_property_type>
%destructor { delete $$; } <tree_classdef_property_list_type>
%destructor { delete $$; } <tree_classdef_properties_block_type>
%destructor { delete $$; } <tree_classdef_methods_list_type>
%destructor { delete $$; } <tree_classdef_methods_block_type>
%destructor { delete $$; } <tree_classdef_event_type>
%destructor { delete $$; } <tree_classdef_events_list_type>
%destructor { delete $$; } <tree_classdef_events_block_type>
%destructor { delete $$; } <tree_classdef_enum_type>
%destructor { delete $$; } <tree_classdef_enum_list_type>
%destructor { delete $$; } <tree_classdef_enum_block_type>

// Defining a generic destructor generates a warning if destructors are
// already explicitly declared for all types.
//
// %destructor {
//    warning_with_id
//      ("Octave:parser-destructor",
//       "possible memory leak in cleanup following parse error");
// } <*>

// Where to start.
%start input

%%

// ==============================
// Statements and statement lists
// ==============================

input           : simple_list '\n'
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = nullptr;

                    if (! parser.finish_input ($1))
                      YYABORT;
                    else
                      YYACCEPT;
                  }
                | simple_list END_OF_INPUT
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = nullptr;

                    if (! parser.finish_input ($1, true))
                      YYABORT;
                    else
                      YYACCEPT;
                  }
                | parse_error
                  {
                    $$ = nullptr;
                    YYABORT;
                  }
                ;

simple_list     : opt_sep_no_nl
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = nullptr;
                  }
                | simple_list1 opt_sep_no_nl
                  { $$ = parser.set_stmt_print_flag ($1, $2, false); }
                ;

simple_list1    : statement
                  { $$ = parser.make_statement_list ($1); }
                | simple_list1 sep_no_nl statement
                  { $$ = parser.append_statement_list ($1, $2, $3, false); }
                ;

opt_list        : // empty
                  { $$ = nullptr; }
                | list
                  { $$ = $1; }
                ;

list            : list1 opt_sep
                  { $$ = parser.set_stmt_print_flag ($1, $2, true); }
                ;

list1           : statement
                  { $$ = parser.make_statement_list ($1); }
                | list1 sep statement
                  { $$ = parser.append_statement_list ($1, $2, $3, true); }
                ;

opt_fcn_list    : // empty
                  { $$ = nullptr; }
                | fcn_list
                  { $$ = $1; }
                ;

fcn_list        : fcn_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                ;

fcn_list1       : function
                  {
                    octave::tree_statement *stmt = parser.make_statement ($1);
                    $$ = parser.make_statement_list (stmt);
                  }
                | fcn_list1 opt_sep function
                  {
                    octave::tree_statement *stmt = parser.make_statement ($3);
                    $$ = parser.append_statement_list ($1, $2, stmt, false);
                  }
                ;

statement       : expression
                  { $$ = parser.make_statement ($1); }
                | command
                  { $$ = parser.make_statement ($1); }
                | word_list_cmd
                  { $$ = parser.make_statement ($1); }
                ;

// =================
// Word-list command
// =================

// These are not really like expressions since they can't appear on
// the RHS of an assignment.  But they are also not like commands (IF,
// WHILE, etc.

word_list_cmd   : identifier word_list
                  {
                    $$ = parser.make_index_expression ($1, $2, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $2.
                        YYABORT;
                      }
                    $$->mark_word_list_cmd ();
                  }
                ;

word_list       : string
                  { $$ = parser.make_argument_list ($1); }
                | word_list string
                  { $$ = parser.append_argument_list ($1, $2); }
                ;

// ===========
// Expressions
// ===========

identifier      : NAME
                  { $$ = parser.make_identifier ($1); }
                ;

superclass_identifier
                : SUPERCLASSREF
                  { $$ = parser.make_superclass_ref ($1); }
                ;

meta_identifier : METAQUERY
                  { $$ = parser.make_metaclass_query ($1); }
                ;

string          : DQ_STRING
                  { $$ = parser.make_constant ($1); }
                | SQ_STRING
                  { $$ = parser.make_constant ($1); }
                ;

constant        : NUMBER
                  { $$ = parser.make_constant ($1); }
                | string
                  { $$ = $1; }
                ;

matrix          : '[' matrix_rows ']'
                  { $$ = parser.finish_matrix ($2, $1, $3); }
                ;

matrix_rows     : cell_or_matrix_row
                  { $$ = parser.make_matrix ($1); }
                | matrix_rows ';' cell_or_matrix_row
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_matrix_row ($1, $3);
                  }
                ;

cell            : '{' cell_rows '}'
                  { $$ = parser.finish_cell ($2, $1, $3); }
                ;

cell_rows       : cell_or_matrix_row
                  { $$ = parser.make_cell ($1); }
                | cell_rows ';' cell_or_matrix_row
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_cell_row ($1, $3);
                  }
                ;

// tree_argument_list objects can't be empty or have leading or trailing
// commas, but those are all allowed in matrix and cell array rows.

// FIXME: is tree_argument_list the best object for this purpose, or
// should we have a separate one intended specifically to represent the
// list of objects that make up elements in cell and matrix expressions?

cell_or_matrix_row
                : // empty
                  { $$ = nullptr; }
                | ','
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = nullptr;
                  }
                | arg_list
                  { $$ = $1; }
                | arg_list ','
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                | ',' arg_list
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = $2;
                  }
                | ',' arg_list ','
                  {
                    OCTAVE_YYUSE ($1, $3);

                    $$ = $2;
                  }
                ;

fcn_handle      : FCN_HANDLE
                  { $$ = parser.make_fcn_handle ($1); }
                ;

// Note that we are deliberately not setting the beginning of statement
// flag after recognizing the parameter list because we don't want to
// accept word list commands in anonymous function bodies.

anon_fcn_handle : '@' param_list anon_fcn_begin expression
                  {
                    $$ = parser.make_anon_fcn_handle ($2, $4, $1->beg_pos ());
                    if (! $$)
                      {
                        // make_anon_fcn_handle deleted $2 and $4.
                        YYABORT;
                      }

                    lexer.m_parsing_anon_fcn_body = false;
                    lexer.m_nesting_level.remove ();
                  }
                | '@' param_list anon_fcn_begin error
                  {
                    OCTAVE_YYUSE ($1, $2);

                    lexer.m_parsing_anon_fcn_body = false;

                    $$ = nullptr;
                    parser.bison_error ("anonymous function bodies must be single expressions");
                    YYABORT;
                  }
                ;

primary_expr    : identifier
                  { $$ = $1; }
                | constant
                  { $$ = $1; }
                | fcn_handle
                  { $$ = $1; }
                | matrix
                  {
                    lexer.m_looking_at_matrix_or_assign_lhs = false;
                    $$ = $1;
                  }
                | cell
                  { $$ = $1; }
                | meta_identifier
                  { $$ = $1; }
                | superclass_identifier
                  { $$ = $1; }
                | '(' expression ')'
                  {
                    OCTAVE_YYUSE ($1, $3);

                    $$ = $2->mark_in_parens ();
                  }
                ;

magic_colon     : ':'
                  { $$ = parser.make_constant ($1); }
                ;

magic_tilde     : EXPR_NOT
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = parser.make_black_hole ();
                  }
                ;

arg_list        : expression
                  { $$ = parser.make_argument_list ($1); }
                | magic_colon
                  { $$ = parser.make_argument_list ($1); }
                | magic_tilde
                  { $$ = parser.make_argument_list ($1); }
                | arg_list ',' magic_colon
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_argument_list ($1, $3);
                  }
                | arg_list ',' magic_tilde
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_argument_list ($1, $3);
                  }
                | arg_list ',' expression
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_argument_list ($1, $3);
                  }
                ;

indirect_ref_op : '.'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = 0;
                    lexer.m_looking_at_indirect_ref = true;
                  }
                ;

oper_expr       : primary_expr
                  { $$ = $1; }
                | oper_expr PLUS_PLUS
                  { $$ = parser.make_postfix_op (PLUS_PLUS, $1, $2); }
                | oper_expr MINUS_MINUS
                  { $$ = parser.make_postfix_op (MINUS_MINUS, $1, $2); }
                | oper_expr '(' ')'
                  {
                    OCTAVE_YYUSE ($2, $3);

                    $$ = parser.make_index_expression ($1, nullptr, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | oper_expr '(' arg_list ')'
                  {
                    OCTAVE_YYUSE ($2, $4);

                    $$ = parser.make_index_expression ($1, $3, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | oper_expr '{' '}'
                  {
                    OCTAVE_YYUSE ($2, $3);

                    $$ = parser.make_index_expression ($1, nullptr, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | oper_expr '{' arg_list '}'
                  {
                    OCTAVE_YYUSE ($2, $4);

                    $$ = parser.make_index_expression ($1, $3, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | oper_expr HERMITIAN
                  { $$ = parser.make_postfix_op (HERMITIAN, $1, $2); }
                | oper_expr TRANSPOSE
                  { $$ = parser.make_postfix_op (TRANSPOSE, $1, $2); }
                | oper_expr indirect_ref_op STRUCT_ELT
                  { $$ = parser.make_indirect_ref ($1, $3->text ()); }
                | oper_expr indirect_ref_op '(' expression ')'
                  {
                    OCTAVE_YYUSE ($3, $5);

                    $$ = parser.make_indirect_ref ($1, $4);
                  }
                | PLUS_PLUS oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); }
                | MINUS_MINUS oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); }
                | EXPR_NOT oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); }
                | '+' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('+', $2, $1); }
                | '-' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('-', $2, $1); }
                | oper_expr POW power_expr
                  { $$ = parser.make_binary_op (POW, $1, $2, $3); }
                | oper_expr EPOW power_expr
                  { $$ = parser.make_binary_op (EPOW, $1, $2, $3); }
                | oper_expr '+' oper_expr
                  { $$ = parser.make_binary_op ('+', $1, $2, $3); }
                | oper_expr '-' oper_expr
                  { $$ = parser.make_binary_op ('-', $1, $2, $3); }
                | oper_expr '*' oper_expr
                  { $$ = parser.make_binary_op ('*', $1, $2, $3); }
                | oper_expr '/' oper_expr
                  { $$ = parser.make_binary_op ('/', $1, $2, $3); }
                | oper_expr EMUL oper_expr
                  { $$ = parser.make_binary_op (EMUL, $1, $2, $3); }
                | oper_expr EDIV oper_expr
                  { $$ = parser.make_binary_op (EDIV, $1, $2, $3); }
                | oper_expr LEFTDIV oper_expr
                  { $$ = parser.make_binary_op (LEFTDIV, $1, $2, $3); }
                | oper_expr ELEFTDIV oper_expr
                  { $$ = parser.make_binary_op (ELEFTDIV, $1, $2, $3); }
                ;

power_expr      : primary_expr
                  { $$ = $1; }
                | power_expr PLUS_PLUS
                  { $$ = parser.make_postfix_op (PLUS_PLUS, $1, $2); }
                | power_expr MINUS_MINUS
                  { $$ = parser.make_postfix_op (MINUS_MINUS, $1, $2); }
                | power_expr '(' ')'
                  {
                    OCTAVE_YYUSE ($2, $3);

                    $$ = parser.make_index_expression ($1, nullptr, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | power_expr '(' arg_list ')'
                  {
                    OCTAVE_YYUSE ($2, $4);

                    $$ = parser.make_index_expression ($1, $3, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | power_expr '{' '}'
                  {
                    OCTAVE_YYUSE ($2, $3);

                    $$ = parser.make_index_expression ($1, nullptr, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | power_expr '{' arg_list '}'
                  {
                    OCTAVE_YYUSE ($2, $4);

                    $$ = parser.make_index_expression ($1, $3, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | power_expr indirect_ref_op STRUCT_ELT
                  { $$ = parser.make_indirect_ref ($1, $3->text ()); }
                | power_expr indirect_ref_op '(' expression ')'
                  {
                    OCTAVE_YYUSE ($3, $5);

                    $$ = parser.make_indirect_ref ($1, $4);
                  }
                | PLUS_PLUS power_expr %prec POW
                  { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); }
                | MINUS_MINUS power_expr %prec POW
                  { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); }
                | EXPR_NOT power_expr %prec POW
                  { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); }
                | '+' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('+', $2, $1); }
                | '-' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('-', $2, $1); }
                ;

colon_expr      : oper_expr ':' oper_expr
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.make_colon_expression ($1, $3);

                    if (! $$)
                      {
                        // make_colon_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | oper_expr ':' oper_expr ':' oper_expr
                  {
                    OCTAVE_YYUSE ($2, $4);

                    $$ = parser.make_colon_expression ($1, $5, $3);

                    if (! $$)
                      {
                        // make_colon_expression deleted $1, $3, and $5.
                        YYABORT;
                      }
                  }
                ;

simple_expr     : oper_expr
                  { $$ = $1; }
                | colon_expr
                  { $$ = $1; }
                | simple_expr EXPR_LT simple_expr
                  { $$ = parser.make_binary_op (EXPR_LT, $1, $2, $3); }
                | simple_expr EXPR_LE simple_expr
                  { $$ = parser.make_binary_op (EXPR_LE, $1, $2, $3); }
                | simple_expr EXPR_EQ simple_expr
                  { $$ = parser.make_binary_op (EXPR_EQ, $1, $2, $3); }
                | simple_expr EXPR_GE simple_expr
                  { $$ = parser.make_binary_op (EXPR_GE, $1, $2, $3); }
                | simple_expr EXPR_GT simple_expr
                  { $$ = parser.make_binary_op (EXPR_GT, $1, $2, $3); }
                | simple_expr EXPR_NE simple_expr
                  { $$ = parser.make_binary_op (EXPR_NE, $1, $2, $3); }
                | simple_expr EXPR_AND simple_expr
                  { $$ = parser.make_binary_op (EXPR_AND, $1, $2, $3); }
                | simple_expr EXPR_OR simple_expr
                  { $$ = parser.make_binary_op (EXPR_OR, $1, $2, $3); }
                | simple_expr EXPR_AND_AND simple_expr
                  { $$ = parser.make_boolean_op (EXPR_AND_AND, $1, $2, $3); }
                | simple_expr EXPR_OR_OR simple_expr
                  { $$ = parser.make_boolean_op (EXPR_OR_OR, $1, $2, $3); }
                ;

assign_lhs      : simple_expr
                  {
                    $$ = parser.validate_matrix_for_assignment ($1);

                    if ($$)
                      { lexer.m_looking_at_matrix_or_assign_lhs = false; }
                    else
                      {
                        // validate_matrix_for_assignment deleted $1.
                        YYABORT;
                      }
                  }
                ;

assign_expr     : assign_lhs '=' expression
                  { $$ = parser.make_assign_op ('=', $1, $2, $3); }
                | assign_lhs ADD_EQ expression
                  { $$ = parser.make_assign_op (ADD_EQ, $1, $2, $3); }
                | assign_lhs SUB_EQ expression
                  { $$ = parser.make_assign_op (SUB_EQ, $1, $2, $3); }
                | assign_lhs MUL_EQ expression
                  { $$ = parser.make_assign_op (MUL_EQ, $1, $2, $3); }
                | assign_lhs DIV_EQ expression
                  { $$ = parser.make_assign_op (DIV_EQ, $1, $2, $3); }
                | assign_lhs LEFTDIV_EQ expression
                  { $$ = parser.make_assign_op (LEFTDIV_EQ, $1, $2, $3); }
                | assign_lhs POW_EQ expression
                  { $$ = parser.make_assign_op (POW_EQ, $1, $2, $3); }
                | assign_lhs EMUL_EQ expression
                  { $$ = parser.make_assign_op (EMUL_EQ, $1, $2, $3); }
                | assign_lhs EDIV_EQ expression
                  { $$ = parser.make_assign_op (EDIV_EQ, $1, $2, $3); }
                | assign_lhs ELEFTDIV_EQ expression
                  { $$ = parser.make_assign_op (ELEFTDIV_EQ, $1, $2, $3); }
                | assign_lhs EPOW_EQ expression
                  { $$ = parser.make_assign_op (EPOW_EQ, $1, $2, $3); }
                | assign_lhs AND_EQ expression
                  { $$ = parser.make_assign_op (AND_EQ, $1, $2, $3); }
                | assign_lhs OR_EQ expression
                  { $$ = parser.make_assign_op (OR_EQ, $1, $2, $3); }
                ;

expression      : simple_expr
                  {
                    if ($1 && ($1->is_matrix () || $1->iscell ()))
                      {
                        if (parser.validate_array_list ($1))
                          $$ = $1;
                        else
                          {
                            delete $1;
                            YYABORT;
                          }
                      }
                    else
                      $$ = $1;
                  }
                | assign_expr
                  {
                    if (! $1)
                      YYABORT;

                    $$ = $1;
                  }
                | anon_fcn_handle
                  { $$ = $1; }
                ;

// ================================================
// Commands, declarations, and function definitions
// ================================================

command         : declaration
                  { $$ = $1; }
                | select_command
                  { $$ = $1; }
                | loop_command
                  { $$ = $1; }
                | jump_command
                  { $$ = $1; }
                | spmd_command
                  { $$ = $1; }
                | except_command
                  { $$ = $1; }
                | function
                  { $$ = $1; }
                | file
                  { $$ = $1; }
                ;

// ======================
// Declaration statements
// ======================

declaration     : GLOBAL decl_init_list
                  {
                    $$ = parser.make_decl_command (GLOBAL, $1, $2);
                    lexer.m_looking_at_decl_list = false;
                  }
                | PERSISTENT decl_init_list
                  {
                    $$ = parser.make_decl_command (PERSISTENT, $1, $2);
                    lexer.m_looking_at_decl_list = false;
                  }
                ;

decl_init_list   : decl_elt
                  { $$ = parser.make_decl_init_list ($1); }
                | decl_init_list decl_elt
                  { $$ = parser.append_decl_init_list ($1, $2); }
                ;

decl_elt        : identifier
                  { $$ = parser.make_decl_elt ($1); }
                | identifier '=' expression
                  { $$ = parser.make_decl_elt ($1, $2, $3); }
                ;

// ====================
// Selection statements
// ====================

select_command  : if_command
                  { $$ = $1; }
                | switch_command
                  { $$ = $1; }
                ;

// ============
// If statement
// ============

if_command      : IF stash_comment if_cmd_list END
                  {
                    if (! ($$ = parser.finish_if_command ($1, $3, $4, $2)))
                      {
                        // finish_if_command deleted $3.
                        YYABORT;
                      }
                  }
                ;

if_cmd_list     : if_cmd_list1
                  { $$ = $1; }
                | if_cmd_list1 else_clause
                  { $$ = parser.append_if_clause ($1, $2); }
                ;

if_cmd_list1    : expression stmt_begin opt_sep opt_list
                  {
                    OCTAVE_YYUSE ($3);

                    $1->mark_braindead_shortcircuit ();

                    $$ = parser.start_if_command ($1, $4);
                  }
                | if_cmd_list1 elseif_clause
                  { $$ = parser.append_if_clause ($1, $2); }
                ;

elseif_clause   : ELSEIF stash_comment opt_sep expression stmt_begin opt_sep opt_list
                  {
                    OCTAVE_YYUSE ($3, $6);

                    $4->mark_braindead_shortcircuit ();

                    $$ = parser.make_elseif_clause ($1, $4, $7, $2);
                  }
                ;

else_clause     : ELSE stash_comment opt_sep opt_list
                  {
                    OCTAVE_YYUSE ($3);

                    $$ = parser.make_else_clause ($1, $2, $4);
                  }
                ;

// ================
// Switch statement
// ================

switch_command  : SWITCH stash_comment expression opt_sep case_list END
                  {
                    OCTAVE_YYUSE ($4);

                    if (! ($$ = parser.finish_switch_command ($1, $3, $5, $6, $2)))
                      {
                        // finish_switch_command deleted $3 adn $5.
                        YYABORT;
                      }
                  }
                ;

case_list       : // empty
                  { $$ = nullptr; }
                | default_case
                  { $$ = parser.make_switch_case_list ($1); }
                | case_list1
                  { $$ = $1; }
                | case_list1 default_case
                  { $$ = parser.append_switch_case ($1, $2); }
                ;

case_list1      : switch_case
                  { $$ = parser.make_switch_case_list ($1); }
                | case_list1 switch_case
                  { $$ = parser.append_switch_case ($1, $2); }
                ;

switch_case     : CASE stash_comment opt_sep expression stmt_begin opt_sep opt_list
                  {
                    OCTAVE_YYUSE ($3, $6);

                    $$ = parser.make_switch_case ($1, $4, $7, $2);
                  }
                ;

default_case    : OTHERWISE stash_comment opt_sep opt_list
                  {
                    OCTAVE_YYUSE ($3);

                    $$ = parser.make_default_switch_case ($1, $2, $4);
                  }
                ;

// =======
// Looping
// =======

loop_command    : WHILE stash_comment expression stmt_begin opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($5);

                    $3->mark_braindead_shortcircuit ();

                    if (! ($$ = parser.make_while_command ($1, $3, $6, $7, $2)))
                      {
                        // make_while_command deleted $3 and $6.
                        YYABORT;
                      }
                  }
                | DO stash_comment opt_sep opt_list UNTIL expression
                  {
                    OCTAVE_YYUSE ($1, $3);

                    $$ = parser.make_do_until_command ($5, $4, $6, $2);
                  }
                | FOR stash_comment assign_lhs '=' expression stmt_begin opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($4, $7);

                    if (! ($$ = parser.make_for_command (FOR, $1, $3, $5,
                                                         nullptr, $8, $9, $2)))
                      {
                        // make_for_command deleted $3, $5, and $8.
                        YYABORT;
                      }
                  }
                | FOR stash_comment '(' assign_lhs '=' expression ')' opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3, $5, $7, $8);

                    if (! ($$ = parser.make_for_command (FOR, $1, $4, $6,
                                                         nullptr, $9, $10, $2)))
                      {
                        // make_for_command deleted $4, $6, and $9.
                        YYABORT;
                      }
                  }
                | PARFOR stash_comment assign_lhs '=' expression stmt_begin opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($4, $7);

                    if (! ($$ = parser.make_for_command (PARFOR, $1, $3, $5,
                                                         nullptr, $8, $9, $2)))
                      {
                        // make_for_command deleted $3, $5, and $8.
                        YYABORT;
                      }
                  }
                | PARFOR stash_comment '(' assign_lhs '=' expression ',' expression ')' opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3, $5, $7, $9, $10);

                    if (! ($$ = parser.make_for_command (PARFOR, $1, $4, $6,
                                                         $8, $11, $12, $2)))
                      {
                        // make_for_command deleted $4, $6, $8, and $11.
                        YYABORT;
                      }
                  }
                ;

// =======
// Jumping
// =======

jump_command    : BREAK
                  {
                    if (! ($$ = parser.make_break_command ($1)))
                      YYABORT;
                  }
                | CONTINUE
                  {
                    if (! ($$ = parser.make_continue_command ($1)))
                      YYABORT;
                  }
                | FUNC_RET
                  { $$ = parser.make_return_command ($1); }
                ;

// =======================
// Parallel execution pool
// =======================

spmd_command    : SPMD stash_comment opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_spmd_command ($1, $4, $5, lc, tc)))
                      {
                        // make_spmd_command deleted $4, LC, and TC.
                        YYABORT;
                      }
                  }
                ;

// ==========
// Exceptions
// ==========

except_command  : UNWIND stash_comment opt_sep opt_list CLEANUP
                  stash_comment opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3, $5, $7);

                    if (! ($$ = parser.make_unwind_command ($1, $4, $8, $9, $2, $6)))
                      {
                        // make_unwind_command deleted $4 and $8.
                        YYABORT;
                      }
                  }
                | TRY stash_comment opt_sep opt_list CATCH stash_comment
                  opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3, $5, $7);

                    if (! ($$ = parser.make_try_command ($1, $4, $7, $8, $9, $2, $6)))
                      {
                        // make_try_command deleted $4 and $8.
                        YYABORT;
                      }
                  }
                | TRY stash_comment opt_sep opt_list END
                  {
                    OCTAVE_YYUSE ($3);

                    if (! ($$ = parser.make_try_command ($1, $4, 0, nullptr,
                                                         $5, $2, nullptr)))
                      {
                        // make_try_command deleted $4.
                        YYABORT;
                      }
                  }
                ;

// ===========================================
// Some 'subroutines' for function definitions
// ===========================================

push_fcn_symtab : // empty
                  {
                    if (! parser.push_fcn_symtab ())
                      YYABORT;

                    $$ = 0;
                  }
                ;

// ===========================
// List of function parameters
// ===========================

param_list_beg  : '('
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = 0;
                    lexer.m_looking_at_parameter_list = true;
                    lexer.m_arguments_is_keyword = false;

                    if (lexer.m_looking_at_function_handle)
                      {
                        // Will get a real name later.
                        lexer.m_symtab_context.push (octave::symbol_scope ("parser:param_list_beg"));
                        lexer.m_looking_at_function_handle--;
                        lexer.m_looking_at_anon_fcn_args = true;
                      }
                  }
                ;

param_list_end  : ')'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = 0;
                    lexer.m_looking_at_parameter_list = false;
                    lexer.m_arguments_is_keyword = true;
                    lexer.m_looking_for_object_index = false;
                  }
                ;

opt_param_list  : // empty
                  { $$ = nullptr; }
                | param_list
                  { $$ = $1; }
                ;

param_list      : param_list_beg param_list1 param_list_end
                  {
                    if ($2)
                      lexer.mark_as_variables ($2->variable_names ());

                    $$ = $2;
                  }
                | param_list_beg error
                  {
                    $$ = nullptr;
                    parser.bison_error ("invalid parameter list");
                    YYABORT;
                  }
                ;

param_list1     : // empty
                  { $$ = parser.make_parameter_list (octave::tree_parameter_list::in); }
                | param_list2
                  {
                    $1->mark_as_formal_parameters ();

                    if (parser.validate_param_list ($1, octave::tree_parameter_list::in))
                      {
                        lexer.mark_as_variables ($1->variable_names ());
                        $$ = $1;
                      }
                    else
                      {
                        delete $1;
                        YYABORT;
                      }
                  }
                ;

param_list2     : param_list_elt
                  { $$ = parser.make_parameter_list (octave::tree_parameter_list::in, $1); }
                | param_list2 ',' param_list_elt
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_parameter_list ($1, $3);
                  }
                ;

param_list_elt  : decl_elt
                  { $$ = $1; }
                | magic_tilde
                  { $$ = parser.make_decl_elt ($1); }
                ;

// ===================================
// List of function return value names
// ===================================

return_list     : '[' ']'
                  {
                    OCTAVE_YYUSE ($1, $2);

                    lexer.m_looking_at_return_list = false;

                    $$ = parser.make_parameter_list (octave::tree_parameter_list::out);
                  }
                | identifier
                  {
                    lexer.m_looking_at_return_list = false;

                    octave::tree_parameter_list *tmp
                      = parser.make_parameter_list (octave::tree_parameter_list::out, $1);

                    // Even though this parameter list can contain only
                    // a single identifier, we still need to validate it
                    // to check for varargin or varargout.

                    if (parser.validate_param_list (tmp, octave::tree_parameter_list::out))
                      $$ = tmp;
                    else
                      {
                        delete tmp;
                        YYABORT;
                      }
                  }
                | '[' return_list1 ']'
                  {
                    OCTAVE_YYUSE ($1, $3);

                    lexer.m_looking_at_return_list = false;

                    // Check for duplicate parameter names, varargin,
                    // or varargout.

                    if (parser.validate_param_list ($2, octave::tree_parameter_list::out))
                      $$ = $2;
                    else
                      {
                        delete $2;
                        YYABORT;
                      }
                  }
                ;

return_list1    : identifier
                  {
                    $$ = parser.make_parameter_list (octave::tree_parameter_list::out, $1);
                  }
                | return_list1 ',' identifier
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_parameter_list ($1, $3);
                  }
                ;

// =======================
// Script or function file
// =======================

parsing_local_fcns
                : // empty
                  { parser.parsing_local_functions (true); }
                ;

push_script_symtab : // empty
                  {
                    $$ = 0;

                    // This scope may serve as the parent scope for local
                    // functions in classdef files..
                    lexer.m_symtab_context.push (octave::symbol_scope ("parser:push_script_symtab"));
                  }
                ;

begin_file      : push_script_symtab INPUT_FILE
                  { $$ = 0; }
                ;

file            : begin_file opt_nl opt_list END_OF_INPUT
                  {
                    OCTAVE_YYUSE ($2);

                    if (lexer.m_reading_fcn_file)
                      {
                        // Delete the dummy statement_list we created
                        // after parsing the function.  Any function
                        // definitions found in the file have already
                        // been stored in the symbol table or in
                        // base_parser::m_primary_fcn.

                        // Unused symbol table context.
                        lexer.m_symtab_context.pop ();

                        delete $3;

                        if (! parser.validate_primary_fcn ())
                          YYABORT;
                      }
                    else
                      {
                        octave::tree_statement *end_of_script
                          = parser.make_end ("endscript", true,
                                             $4->beg_pos (), $4->end_pos ());

                        parser.make_script ($3, end_of_script);

                        if (! parser.validate_primary_fcn ())
                          YYABORT;
                      }

                    $$ = nullptr;
                  }
                | begin_file opt_nl classdef parsing_local_fcns opt_sep opt_fcn_list END_OF_INPUT
                  {
                    OCTAVE_YYUSE ($2, $5, $7);

                    // Unused symbol table context.
                    lexer.m_symtab_context.pop ();

                    if (! parser.finish_classdef_file ($3, $6))
                      YYABORT;

                    $$ = nullptr;
                  }
                ;

// ===================
// Function definition
// ===================

function_beg    : push_fcn_symtab FCN
                  {
                    $$ = $2;
                    if (lexer.m_reading_classdef_file
                        || lexer.m_parsing_classdef)
                      lexer.m_maybe_classdef_get_set_method = true;
                  }
                ;

fcn_name        : identifier
                  {
                    if (! ($$ = parser.make_fcn_name ($1)))
                      {
                        // make_fcn_name deleted $1.
                        YYABORT;
                      }

                    lexer.m_arguments_is_keyword = true;
                  }
                | GET '.' identifier
                  {
                    OCTAVE_YYUSE ($1, $2);

                    $$ = $3;

                    lexer.m_parsed_function_name.top () = true;
                    lexer.m_maybe_classdef_get_set_method = false;
                    lexer.m_parsing_classdef_get_method = true;
                    lexer.m_arguments_is_keyword = true;
                  }
                | SET '.' identifier
                  {
                    OCTAVE_YYUSE ($1, $2);

                    $$ = $3;

                    lexer.m_parsed_function_name.top () = true;
                    lexer.m_maybe_classdef_get_set_method = false;
                    lexer.m_parsing_classdef_set_method = true;
                    lexer.m_arguments_is_keyword = true;
                  }
                ;

function_end    : END
                  {
                    parser.endfunction_found (true);

                    if (parser.end_token_ok ($1, octave::token::function_end))
                      $$ = parser.make_end ("endfunction", false,
                                            $1->beg_pos (), $1->end_pos ());
                    else
                      {
                        parser.end_token_error ($1, octave::token::function_end);
                        YYABORT;
                      }
                  }
                | END_OF_INPUT
                  {
// A lot of tests are based on the assumption that this is OK
//                  if (lexer.m_reading_script_file)
//                    {
//                      parser.bison_error ("function body open at end of script");
//                      YYABORT;
//                    }

                    if (parser.endfunction_found ())
                      {
                        parser.bison_error ("inconsistent function endings -- "
                                 "if one function is explicitly ended, "
                                 "so must all the others");
                        YYABORT;
                      }

                    if (! (lexer.m_reading_fcn_file || lexer.m_reading_script_file
                           || lexer.input_from_eval_string ()))
                      {
                        parser.bison_error ("function body open at end of input");
                        YYABORT;
                      }

                    if (lexer.m_reading_classdef_file)
                      {
                        parser.bison_error ("classdef body open at end of input");
                        YYABORT;
                      }

                    $$ = parser.make_end ("endfunction", true,
                                          $1->beg_pos (), $1->end_pos ());
                  }
                ;

function        : function_beg stash_comment fcn_name
                  opt_param_list opt_sep function_body function_end
                  {
                    OCTAVE_YYUSE ($5);

                    $$ = parser.make_function ($1, nullptr, $3, $4, $6, $7, $2);
                  }
                | function_beg stash_comment return_list '=' fcn_name
                  opt_param_list opt_sep function_body function_end
                  {
                    OCTAVE_YYUSE ($4, $7);

                    $$ = parser.make_function ($1, $3, $5, $6, $8, $9, $2);
                  }
                ;

function_body   : at_first_executable_stmt opt_list
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = $2;
                  }
                | function_body1 opt_sep at_first_executable_stmt opt_list
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_function_body ($1, $4);
                  }
                ;

at_first_executable_stmt
                : // empty
                  {
                    $$ = 0;
                    lexer.m_arguments_is_keyword = false;
                  }
                ;

function_body1  : arguments_block
                  {
                    octave::tree_statement *stmt = parser.make_statement ($1);

                    $$ = parser.make_statement_list (stmt);
                  }
                | function_body1 opt_sep arguments_block
                  {
                    octave::tree_statement *stmt = parser.make_statement ($3);

                    $$ = parser.append_statement_list ($1, $2, stmt, false);
                  }
                ;

arguments_block : arguments_beg stash_comment opt_sep args_attr_list
                  args_validation_list opt_sep END
                  {
                    OCTAVE_YYUSE ($3, $6);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_arguments_block ($1, $4, $5, $7, lc, tc)))
                      {
                        // make_arguments_block deleted $4, $5, LC, and TC.
                        YYABORT;
                      }

                    lexer.m_arguments_is_keyword = true;
                  }
                ;

arguments_beg   : ARGUMENTS
                  {
                    $$ = $1;
                    lexer.m_arguments_is_keyword = false;
                  }
                ;

args_attr_list  : // empty
                  { $$ = nullptr; }
                | '(' identifier ')'
                  {
                    OCTAVE_YYUSE ($1, $3);

                    // Error if $$ is nullptr.
                    if  (! ($$ = parser.make_args_attribute_list ($2)))
                      {
                        // make_args_attribute_list deleted $2.
                        YYABORT;
                      }
                  }
                ;

args_validation_list
                  : arg_name arg_validation
                    {
                      $2->arg_name ($1);
                      $$ = parser.make_args_validation_list ($2);
                    }
                  | args_validation_list sep arg_name arg_validation
                    {
                      OCTAVE_YYUSE ($2);

                      $4->arg_name ($3);
                      $$ = parser.append_args_validation_list ($1, $4);
                    }
                  ;

// FIXME: Change grammar to allow IDENTIFIER to be be either
// "NAME" or "NAME '.' NAME", possibly not entered in the symbol
// table for the current scope.  Also stash comments before identifier.

arg_name          : identifier
                    { $$ = $1; }
                  ;

arg_validation    : size_spec class_name validation_fcns default_value
                  {
                    if (! ($$ = parser.make_arg_validation ($1, $2, $3, $4)))
                      {
                        // make_arg_validation deleted ...
                        YYABORT;
                      }
                  }
                ;

size_spec       : // empty
                  { $$ = nullptr; }
                | '(' arg_list ')'
                  {
                    OCTAVE_YYUSE ($1, $3);

                    if (! ($$ = parser.make_arg_size_spec ($2)))
                      {
                        // make_arg_size_spec deleted $2.
                        YYABORT;
                      }
                  }
                ;

class_name      : // empty
                  { $$ = nullptr; }
                | identifier
                  { $$ = $1; }
                ;

// Use argument list so we can accept anonymous functions.
validation_fcns : // empty
                  { $$ = nullptr; }
                | '{' arg_list '}'
                  {
                    OCTAVE_YYUSE ($1, $3);

                    if (! ($$ = parser.make_arg_validation_fcns ($2)))
                      {
                        // make_arg_validation_fcns deleted $2.
                        YYABORT;
                      }
                  }
                ;

default_value   : // empty
                  { $$ = nullptr; }
                | '=' expression
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = $2;
                  }
                ;

// ========
// Classdef
// ========

classdef_beg    : CLASSDEF
                  {
                    if (! lexer.m_reading_classdef_file)
                      {
                        parser.bison_error ("classdef must appear inside a file containing only a class definition");
                        YYABORT;
                      }

                    // Create invalid parent scope.
                    lexer.m_symtab_context.push (octave::symbol_scope ());
                    lexer.m_parsing_classdef = true;
                    lexer.m_parsing_classdef_decl = true;
                    lexer.m_classdef_element_names_are_keywords = true;

                    $$ = $1;
                  }
                ;

classdef        : classdef_beg stash_comment attr_list identifier opt_sep superclass_list class_body END
                  {
                    OCTAVE_YYUSE ($5);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    lexer.m_parsing_classdef = false;

                    if (! ($$ = parser.make_classdef ($1, $3, $4, $6, $7, $8,
                                                      lc, tc)))
                      {
                        // make_classdef deleted $3, $4, $6, $7, LC, and
                        // TC.
                        YYABORT;
                      }
                  }
                ;

attr_list       : // empty
                  { $$ = nullptr; }
                | '(' attr_list1 ')' opt_sep
                  {
                    OCTAVE_YYUSE ($1, $3, $4);

                    $$ = $2;
                  }
                ;

attr_list1      : attr
                  { $$ = parser.make_classdef_attribute_list ($1); }
                | attr_list1 ',' attr
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_attribute ($1, $3);
                  }
                ;

attr            : identifier
                  { $$ = parser.make_classdef_attribute ($1); }
                | identifier '=' expression
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.make_classdef_attribute ($1, $3);
                  }
                | EXPR_NOT identifier
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = parser.make_not_classdef_attribute ($2);
                  }
                ;

superclass_list : // empty
                  {
                    lexer.m_parsing_classdef_decl = false;
                    lexer.m_parsing_classdef_superclass = false;

                    $$ = nullptr;
                  }
                | superclass_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_parsing_classdef_decl = false;
                    lexer.m_parsing_classdef_superclass = false;

                    $$ = $1;
                  }
                ;

superclass_list1
                : EXPR_LT superclass
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = parser.make_classdef_superclass_list ($2);
                  }
                | superclass_list1 EXPR_AND superclass
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_superclass ($1, $3);
                  }
                ;

superclass      : FQ_IDENT
                  { $$ = parser.make_classdef_superclass ($1); }
                ;

class_body      : // empty
                  {
                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = nullptr;
                  }
                | class_body1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = $1;
                  }
                ;

class_body1     : properties_block
                  { $$ = parser.make_classdef_body ($1); }
                | methods_block
                  { $$ = parser.make_classdef_body ($1); }
                | events_block
                  { $$ = parser.make_classdef_body ($1); }
                | enum_block
                  { $$ = parser.make_classdef_body ($1); }
                | class_body1 opt_sep properties_block
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_properties_block ($1, $3);
                  }
                | class_body1 opt_sep methods_block
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_methods_block ($1, $3);
                  }
                | class_body1 opt_sep events_block
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_events_block ($1, $3);
                  }
                | class_body1 opt_sep enum_block
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_enum_block ($1, $3);
                  }
                ;

properties_block
                : properties_beg stash_comment opt_sep attr_list property_list END
                  {
                    OCTAVE_YYUSE ($3);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_classdef_properties_block
                           ($1, $4, $5, $6, lc, tc)))
                      {
                        // make_classdef_properties_block deleted $4,
                        // $5, LC, and TC.
                        YYABORT;
                      }
                  }
                ;

properties_beg  : PROPERTIES
                  {
                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = $1;
                  }
                ;

property_list   : // empty
                  {
                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = nullptr;
                  }
                | property_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = $1;
                  }
                ;

property_list1
                : class_property
                  { $$ = parser.make_classdef_property_list ($1); }
                | property_list1 sep class_property
                  {
                    OCTAVE_YYUSE ($2);

                    // We don't look ahead to grab end-of-line comments.
                    // Instead, they are grabbed when we see the
                    // identifier that becomes the next element in the
                    // list.  If the element at the end of the list
                    // doesn't have a doc string, see whether the
                    // element we are adding is stroing an end-of-line
                    // comment for us to use.

                    octave::tree_classdef_property *last_elt = $1->back ();

                    if (! last_elt->have_doc_string ())
                      {
                        octave::comment_list *cl = $3->comments ();

                        if (cl)
                          {
                            octave::comment_elt elt = cl->front ();

                            if (elt.is_end_of_line ())
                              last_elt->doc_string (elt.text ());
                          }
                      }

                    $$ = parser.append_classdef_property ($1, $3);
                  }
                ;

class_property  : stash_comment identifier arg_validation
                  { $$ = parser.make_classdef_property ($1, $2, $3); }
                ;

methods_block   : methods_beg stash_comment opt_sep attr_list methods_list END
                  {
                    OCTAVE_YYUSE ($3);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_classdef_methods_block
                           ($1, $4, $5, $6, lc, tc)))
                      {
                        // make_classdef_methods_block deleted $4, $5,
                        // LC, and TC.
                        YYABORT;
                      }
                  }
                ;

methods_beg     : METHODS
                  {
                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = $1;
                  }
                ;

method_decl1    : identifier
                  {
                    if (! ($$ = parser.start_classdef_external_method ($1, nullptr)))
                      YYABORT;
                  }
                | identifier param_list
                  {
                    if (! ($$ = parser.start_classdef_external_method ($1, $2)))
                      YYABORT;
                  }
                ;

method_decl     : stash_comment method_decl1
                  { $$ = parser.finish_classdef_external_method ($2, nullptr, $1); }
                | stash_comment return_list '='
                  {
                    OCTAVE_YYUSE ($3);

                    lexer.m_defining_func++;
                    lexer.m_parsed_function_name.push (false);
                  }
                  method_decl1
                  {
                    lexer.m_defining_func--;
                    lexer.m_parsed_function_name.pop ();

                    $$ = parser.finish_classdef_external_method ($5, $2, $1);
                  }
                ;

method          : method_decl
                  { $$ = $1; }
                | function
                  { $$ = $1; }
                ;

methods_list    : // empty
                  {
                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = nullptr;
                  }
                | methods_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = $1;
                  }
                ;

methods_list1   : method
                  { $$ = parser.make_classdef_methods_list ($1); }
                | methods_list1 opt_sep method
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_method ($1, $3);
                  }
                ;

events_block    : events_beg stash_comment opt_sep attr_list events_list END
                  {
                    OCTAVE_YYUSE ($3);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_classdef_events_block
                           ($1, $4, $5, $6, lc, tc)))
                      {
                        // make_classdef_events_block deleted $4, $5,
                        // LC, and TC.
                        YYABORT;
                      }
                  }
                ;

events_beg      : EVENTS
                  {
                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = $1;
                  }
                ;

events_list     : // empty
                  {
                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = nullptr;
                  }
                | events_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = $1;
                  }
                ;

events_list1    : class_event
                  { $$ = parser.make_classdef_events_list ($1); }
                | events_list1 opt_sep class_event
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_event ($1, $3);
                  }
                ;

class_event     : stash_comment identifier
                  { $$ = parser.make_classdef_event ($1, $2); }
                ;

enum_block      : enumeration_beg stash_comment opt_sep attr_list enum_list END
                  {
                    OCTAVE_YYUSE ($3);

                    octave::comment_list *lc = $2;
                    octave::comment_list *tc = lexer.get_comment ();

                    if (! ($$ = parser.make_classdef_enum_block
                           ($1, $4, $5, $6, lc, tc)))
                      {
                        // make_classdef_enum_block deleted $4, $5, LC,
                        // and TC.
                        YYABORT;
                      }
                  }
                ;

enumeration_beg : ENUMERATION
                  {
                    lexer.m_classdef_element_names_are_keywords = false;
                    $$ = $1;
                  }
                ;

enum_list       : // empty
                  {
                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = nullptr;
                  }
                | enum_list1 opt_sep
                  {
                    OCTAVE_YYUSE ($2);

                    lexer.m_classdef_element_names_are_keywords = true;
                    $$ = $1;
                  }
                ;

enum_list1      : class_enum
                  { $$ = parser.make_classdef_enum_list ($1); }
                | enum_list1 opt_sep class_enum
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = parser.append_classdef_enum ($1, $3);
                  }
                ;

class_enum      : stash_comment identifier '(' expression ')'
                  {
                    OCTAVE_YYUSE ($3, $5);

                    $$ = parser.make_classdef_enum ($2, $4, $1);
                  }
                ;

// =============
// Miscellaneous
// =============

stmt_begin      : // empty
                  {
                    $$ = 0;
                    lexer.m_at_beginning_of_statement = true;
                  }
                ;

anon_fcn_begin  : // empty
                  {
                    $$ = 0;
                    lexer.m_at_beginning_of_statement = true;
                    lexer.m_parsing_anon_fcn_body = true;
                  }
                ;

stash_comment   : // empty
                  { $$ = lexer.get_comment (); }
                ;

parse_error     : LEXICAL_ERROR
                  {
                    $$ = 0;
                    std::string msg = $1->text ();
                    parser.bison_error (msg.c_str ());
                  }
                | error
                  { $$ = 0; }
                ;

sep_no_nl       : ','
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = ',';
                  }
                | ';'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = ';';
                  }
                | sep_no_nl ','
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                | sep_no_nl ';'
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                ;

opt_sep_no_nl   : // empty
                  { $$ = 0; }
                | sep_no_nl
                  { $$ = $1; }
                ;

opt_nl          : // empty
                  { $$ = 0; }
                | nl
                  { $$ = $1; }
                ;

nl              : '\n'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = '\n';
                  }
                | nl '\n'
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                ;

sep             : ','
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = ',';
                  }
                | ';'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = ';';
                  }
                | '\n'
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = '\n';
                  }
                | sep ','
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                | sep ';'
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                | sep '\n'
                  {
                    OCTAVE_YYUSE ($2);

                    $$ = $1;
                  }
                ;

opt_sep         : // empty
                  { $$ = 0; }
                | sep
                  { $$ = $1; }
                ;

%%

#if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC)
   // Restore prevailing warning state for remainder of the file.
#  pragma GCC diagnostic pop
#endif

// Generic error messages.

#undef lexer
#undef scanner

static void
yyerror (octave::base_parser& parser, const char *s)
{
  parser.bison_error (s);
}

OCTAVE_NAMESPACE_BEGIN

  class parse_exception : public std::runtime_error
  {
  public:

    parse_exception (const std::string& message,
                     const std::string& fcn_name = "",
                     const std::string& file_name = "",
                     int line = -1, int column = -1)
      : runtime_error (message), m_message (message),
        m_fcn_name (fcn_name), m_file_name (file_name),
        m_line (line), m_column (column)
    { }

    parse_exception (const parse_exception&) = default;

    parse_exception& operator = (const parse_exception&) = default;

    ~parse_exception (void) = default;

    std::string message (void) const { return m_message; }

    // Provided for std::exception interface.
    const char * what (void) const noexcept { return m_message.c_str (); }

    std::string fcn_name (void) const { return m_fcn_name; }
    std::string file_name (void) const { return m_file_name; }

    int line (void) const { return m_line; }
    int column (void) const { return m_column; }

    // virtual void display (std::ostream& os) const;

  private:

    std::string m_message;

    std::string m_fcn_name;
    std::string m_file_name;
    int m_line;
    int m_column;
  };

  class parse_tree_validator : public tree_walker
  {
  public:

    parse_tree_validator (void)
      : m_scope (), m_error_list ()
    { }

    parse_tree_validator (const parse_tree_validator&) = delete;

    parse_tree_validator& operator = (const parse_tree_validator&) = delete;

    ~parse_tree_validator (void) = default;

    symbol_scope get_scope (void) const { return m_scope; }

    bool ok (void) const { return m_error_list.empty (); }

    std::list<parse_exception> error_list (void) const
    {
      return m_error_list;
    }

    void visit_octave_user_script (octave_user_script& script)
    {
      unwind_protect_var<symbol_scope> restore_var (m_scope, script.scope ());

      tree_statement_list *stmt_list = script.body ();

      if (stmt_list)
        stmt_list->accept (*this);
    }

    void visit_octave_user_function (octave_user_function& fcn)
    {
      unwind_protect_var<symbol_scope> restore_var (m_scope, fcn.scope ());

      tree_statement_list *stmt_list = fcn.body ();

      if (stmt_list)
        stmt_list->accept (*this);

      std::map<std::string, octave_value> subfcns = fcn.subfunctions ();

      if (! subfcns.empty ())
        {
          for (auto& nm_val : subfcns)
            {
              octave_user_function *subfcn
                = nm_val.second.user_function_value ();

              if (subfcn)
                subfcn->accept (*this);
            }
        }
    }

    void visit_index_expression (tree_index_expression& idx_expr)
    {
      if (idx_expr.is_word_list_cmd ())
        {
          std::string sym_nm = idx_expr.name ();

          if (m_scope.is_variable (sym_nm))
            {
              std::string message
                = sym_nm + ": invalid use of symbol as both variable and command";
              parse_exception pe (message, m_scope.fcn_name (),
                                  m_scope.fcn_file_name (),
                                  idx_expr.line (), idx_expr.column ());

              m_error_list.push_back (pe);
            }
        }
    }

  private:

    symbol_scope m_scope;

    std::list<parse_exception> m_error_list;
  };

  template <typename LIST_T, typename ELT_T>
  static LIST_T *
  list_append (LIST_T *list, ELT_T elt)
  {
    list->append (elt);
    return list;
  }

  std::size_t
  base_parser::parent_scope_info::size (void) const
  {
    return m_info.size ();
  }

  void
  base_parser::parent_scope_info::push (const value_type& elt)
  {
    m_info.push_back (elt);
  }

  void
  base_parser::parent_scope_info::push (const symbol_scope& scope)
  {
    push (value_type (scope, ""));
  }

  void
  base_parser::parent_scope_info::pop (void)
  {
    m_info.pop_back ();
  }

  bool
  base_parser::parent_scope_info::name_ok (const std::string& name)
  {
    // Name can't be the same as any parent function or any other
    // function we've already seen.  We could maintain a complex
    // tree structure of names, or we can just store the set of
    // full names of all the functions, which must be unique.

    std::string full_name;

    for (std::size_t i = 0; i < size()-1; i++)
      {
        const value_type& elt = m_info[i];

        if (name == elt.second)
          return false;

        full_name += elt.second + ">";
      }

    full_name += name;

    if (m_all_names.find (full_name) != m_all_names.end ())
      {
        // Return false (failure) if we are parsing a subfunction, local
        // function, or nested function.  Otherwise, it is OK to have a
        // duplicate name.

        return ! (m_parser.parsing_subfunctions ()
                  || m_parser.parsing_local_functions ()
                  || m_parser.curr_fcn_depth () > 0);
      }

    m_all_names.insert (full_name);

    return true;
  }

  bool
  base_parser::parent_scope_info::name_current_scope (const std::string& name)
  {
    if (! name_ok (name))
      return false;

    if (size () > 0)
      m_info.back().second = name;

    return true;
  }

  symbol_scope
  base_parser::parent_scope_info::parent_scope (void) const
  {
    return size () > 1 ? m_info[size()-2].first : symbol_scope ();
  }

  std::string
  base_parser::parent_scope_info::parent_name (void) const
  {
    return m_info[size()-2].second;
  }

  void base_parser::parent_scope_info::clear (void)
  {
    m_info.clear ();
    m_all_names.clear ();
  }

  base_parser::base_parser (base_lexer& lxr)
    : m_endfunction_found (false), m_autoloading (false),
      m_fcn_file_from_relative_lookup (false),
      m_parsing_subfunctions (false), m_parsing_local_functions (false),
      m_max_fcn_depth (-1), m_curr_fcn_depth (-1), m_primary_fcn_scope (),
      m_curr_class_name (), m_curr_package_name (), m_function_scopes (*this),
      m_primary_fcn (), m_subfunction_names (), m_classdef_object (),
      m_stmt_list (), m_lexer (lxr), m_parser_state (yypstate_new ())
  { }

  base_parser::~base_parser (void)
  {
    delete &m_lexer;

    // FIXME: Deleting the internal Bison parser state structure does
    // not clean up any partial parse trees in the event of an interrupt or
    // error.  It's not clear how to safely do that with the C language
    // parser that Bison generates.  The C++ language parser that Bison
    // generates would do it for us automatically whenever an exception
    // is thrown while parsing input, but there is currently no C++
    // interface for a push parser.

    yypstate_delete (static_cast<yypstate *> (m_parser_state));
  }

  void
  base_parser::reset (void)
  {
    m_endfunction_found = false;
    m_autoloading = false;
    m_fcn_file_from_relative_lookup = false;
    m_parsing_subfunctions = false;
    m_parsing_local_functions = false;
    m_max_fcn_depth = -1;
    m_curr_fcn_depth = -1;
    m_primary_fcn_scope = symbol_scope ();
    m_curr_class_name = "";
    m_curr_package_name = "";
    m_function_scopes.clear ();
    m_primary_fcn = octave_value ();
    m_subfunction_names.clear ();
    m_classdef_object.reset ();
    m_stmt_list.reset ();

    m_lexer.reset ();

    yypstate_delete (static_cast<yypstate *> (m_parser_state));
    m_parser_state = yypstate_new ();
  }

  // Error messages for mismatched end tokens.

  static std::string
  end_token_as_string (token::end_tok_type ettype)
  {
    std::string retval = "<unknown>";

    switch (ettype)
      {
      case token::simple_end:
        retval = "end";
        break;

      case token::classdef_end:
        retval = "endclassdef";
        break;

      case token::enumeration_end:
        retval = "endenumeration";
        break;

      case token::events_end:
        retval = "endevents";
        break;

      case token::for_end:
        retval = "endfor";
        break;

      case token::function_end:
        retval = "endfunction";
        break;

      case token::if_end:
        retval = "endif";
        break;

      case token::methods_end:
        retval = "endmethods";
        break;

      case token::parfor_end:
        retval = "endparfor";
        break;

      case token::properties_end:
        retval = "endproperties";
        break;

      case token::spmd_end:
        retval = "endspmd";
        break;

      case token::switch_end:
        retval = "endswitch";
        break;

      case token::try_catch_end:
        retval = "end_try_catch";
        break;

      case token::unwind_protect_end:
        retval = "end_unwind_protect";
        break;

      case token::while_end:
        retval = "endwhile";
        break;

      default:
        panic_impossible ();
        break;
      }

    return retval;
  }

  void
  base_parser::statement_list (std::shared_ptr<tree_statement_list>& lst)
  {
    if (! lst)
      return;

    if (m_stmt_list)
      {
        // Append additional code to existing statement list.

        while (! lst->empty ())
          {
            m_stmt_list->push_back (lst->front ());
            lst->pop_front ();
          }
      }
    else
      m_stmt_list = lst;
  }

  void
  base_parser::end_token_error (token *tok, token::end_tok_type expected)
  {
    std::string msg = ("'" + end_token_as_string (expected)
                       + "' command matched by '"
                       + end_token_as_string (tok->ettype ()) + "'");

    bison_error (msg, tok->beg_pos ());
  }

  // Check to see that end tokens are properly matched.

  bool
  base_parser::end_token_ok (token *tok, token::end_tok_type expected)
  {
    token::end_tok_type ettype = tok->ettype ();

    return ettype == expected || ettype == token::simple_end;
  }

  bool
  base_parser::push_fcn_symtab (void)
  {
    m_curr_fcn_depth++;

    if (m_max_fcn_depth < m_curr_fcn_depth)
      m_max_fcn_depth = m_curr_fcn_depth;

    // Will get a real name later.
    m_lexer.m_symtab_context.push (symbol_scope ("parser:push_fcn_symtab"));
    m_function_scopes.push (m_lexer.m_symtab_context.curr_scope ());

    if (! m_lexer.m_reading_script_file && m_curr_fcn_depth == 0
        && ! m_parsing_subfunctions)
        {
          m_primary_fcn_scope = m_lexer.m_symtab_context.curr_scope ();
          m_primary_fcn_scope.mark_primary_fcn_scope ();
        }

    if (m_lexer.m_reading_script_file && m_curr_fcn_depth > 0)
      {
        bison_error ("nested functions not implemented in this context");

        return false;
      }

    return true;
  }

  // Make a constant.

  tree_constant *
  base_parser::make_constant (token *tok_val)
  {
    int l = tok_val->line ();
    int c = tok_val->column ();

    int op = tok_val->token_value ();

    tree_constant *retval = nullptr;

    switch (op)
      {
      case ':':
        {
          octave_value tmp (octave_value::magic_colon_t);
          retval = new tree_constant (tmp);
        }
        break;

      case NUMBER:
        {
          retval = new tree_constant (tok_val->number (), l, c);
          retval->stash_original_text (tok_val->text_rep ());
        }
        break;

      case DQ_STRING:
      case SQ_STRING:
        {
          std::string txt = tok_val->text ();

          char delim = op == DQ_STRING ? '"' : '\'';
          octave_value tmp (txt, delim);

          if (txt.empty ())
            {
              if (op == DQ_STRING)
                tmp = octave_null_str::instance;
              else
                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
          // tok_val->text_rep () for character strings?
          retval->stash_original_text (delim + txt + delim);
        }
        break;

      default:
        panic_impossible ();
        break;
      }

    return retval;
  }

  tree_black_hole *
  base_parser::make_black_hole (void)
  {
    return new tree_black_hole ();
  }

  // Make a function handle.

  tree_fcn_handle *
  base_parser::make_fcn_handle (token *tok_val)
  {
    int l = tok_val->line ();
    int c = tok_val->column ();

    tree_fcn_handle *retval = new tree_fcn_handle (tok_val->text (), l, c);

    return retval;
  }

  // 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)
  {
    // FIXME: We need to examine EXPR and issue an error if any
    // sub-expression contains an assignment, compound assignment,
    // increment, or decrement operator.

    anon_fcn_validator validator (param_list, expr);

    if (! validator.ok ())
      {
        delete param_list;
        delete expr;

        bison_error (validator.message (), validator.line (),
                     validator.column ());

        return nullptr;
      }

    symbol_scope fcn_scope = m_lexer.m_symtab_context.curr_scope ();
    symbol_scope parent_scope = m_lexer.m_symtab_context.parent_scope ();

    m_lexer.m_symtab_context.pop ();

    expr->set_print_flag (false);

    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);

    std::ostringstream buf;

    tree_print_code tpc (buf);

    retval->accept (tpc);

    std::string file = m_lexer.m_fcn_file_full_name;
    if (! file.empty ())
      buf << ": file: " << file;
    else if (m_lexer.input_from_terminal ())
      buf << ": *terminal input*";
    else if (m_lexer.input_from_eval_string ())
      buf << ": *eval string*";
    buf << ": line: " << at_line << " column: " << at_column;

    std::string scope_name = buf.str ();

    fcn_scope.cache_name (scope_name);

    // FIXME: Stash the filename.  This does not work and produces
    // errors when executed.
    //retval->stash_file_name (m_lexer.m_fcn_file_name);

    return retval;
  }

  // Build a colon expression.

  tree_expression *
  base_parser::make_colon_expression (tree_expression *base,
                                      tree_expression *limit,
                                      tree_expression *incr)
  {
    tree_expression *retval = nullptr;

    if (! base || ! limit)
      {
        delete base;
        delete limit;
        delete incr;

        return retval;
      }

    int l = base->line ();
    int c = base->column ();

    tree_colon_expression *expr
      = new tree_colon_expression (base, limit, incr, l, c);

    retval = expr;

    if (base->is_constant () && limit->is_constant ()
        && (! incr || incr->is_constant ()))
      {
        interpreter& interp = __get_interpreter__ ("finish_colon_expression");

        try
          {
            // If the evaluation generates a warning message, restore
            // the previous value of last_warning_message and skip the
            // conversion to a constant value.

            error_system& es = interp.get_error_system ();

            unwind_action restore_last_warning_message
              (&error_system::set_last_warning_message, &es,
               es.last_warning_message (""));

            unwind_action restore_discard_warning_messages
              (&error_system::set_discard_warning_messages, &es,
               es.discard_warning_messages (true));

            tree_evaluator& tw = interp.get_evaluator ();

            octave_value tmp = expr->evaluate (tw);

            std::string msg = es.last_warning_message ();

            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 ());

                delete expr;

                retval = tc_retval;
              }
          }
        catch (const execution_exception&)
          {
            interp.recover_from_exception ();
          }
      }

    return retval;
  }

  // Build a binary expression.

  tree_expression *
  base_parser::make_binary_op (int op, tree_expression *op1,
                               token *tok_val, tree_expression *op2)
  {
    octave_value::binary_op t = octave_value::unknown_binary_op;

    switch (op)
      {
      case POW:
        t = octave_value::op_pow;
        break;

      case EPOW:
        t = octave_value::op_el_pow;
        break;

      case '+':
        t = octave_value::op_add;
        break;

      case '-':
        t = octave_value::op_sub;
        break;

      case '*':
        t = octave_value::op_mul;
        break;

      case '/':
        t = octave_value::op_div;
        break;

      case EMUL:
        t = octave_value::op_el_mul;
        break;

      case EDIV:
        t = octave_value::op_el_div;
        break;

      case LEFTDIV:
        t = octave_value::op_ldiv;
        break;

      case ELEFTDIV:
        t = octave_value::op_el_ldiv;
        break;

      case EXPR_LT:
        t = octave_value::op_lt;
        break;

      case EXPR_LE:
        t = octave_value::op_le;
        break;

      case EXPR_EQ:
        t = octave_value::op_eq;
        break;

      case EXPR_GE:
        t = octave_value::op_ge;
        break;

      case EXPR_GT:
        t = octave_value::op_gt;
        break;

      case EXPR_NE:
        t = octave_value::op_ne;
        break;

      case EXPR_AND:
        t = octave_value::op_el_and;
        break;

      case EXPR_OR:
        t = octave_value::op_el_or;
        break;

      default:
        panic_impossible ();
        break;
      }

    int l = tok_val->line ();
    int c = tok_val->column ();

    return maybe_compound_binary_expression (op1, op2, l, c, t);
  }

  // Build a boolean expression.

  tree_expression *
  base_parser::make_boolean_op (int op, tree_expression *op1,
                                token *tok_val, tree_expression *op2)
  {
    tree_boolean_expression::type t;

    switch (op)
      {
      case EXPR_AND_AND:
        t = tree_boolean_expression::bool_and;
        break;

      case EXPR_OR_OR:
        t = tree_boolean_expression::bool_or;
        break;

      default:
        panic_impossible ();
        break;
      }

    int l = tok_val->line ();
    int c = tok_val->column ();

    return new tree_boolean_expression (op1, op2, l, c, t);
  }

  // Build a prefix expression.

  tree_expression *
  base_parser::make_prefix_op (int op, tree_expression *op1, token *tok_val)
  {
    octave_value::unary_op t = octave_value::unknown_unary_op;

    switch (op)
      {
      case EXPR_NOT:
        t = octave_value::op_not;
        break;

      case '+':
        t = octave_value::op_uplus;
        break;

      case '-':
        t = octave_value::op_uminus;
        break;

      case PLUS_PLUS:
        t = octave_value::op_incr;
        break;

      case MINUS_MINUS:
        t = octave_value::op_decr;
        break;

      default:
        panic_impossible ();
        break;
      }

    int l = tok_val->line ();
    int c = tok_val->column ();

    return new tree_prefix_expression (op1, l, c, t);
  }

  // Build a postfix expression.

  tree_expression *
  base_parser::make_postfix_op (int op, tree_expression *op1, token *tok_val)
  {
    octave_value::unary_op t = octave_value::unknown_unary_op;

    switch (op)
      {
      case HERMITIAN:
        t = octave_value::op_hermitian;
        break;

      case TRANSPOSE:
        t = octave_value::op_transpose;
        break;

      case PLUS_PLUS:
        t = octave_value::op_incr;
        break;

      case MINUS_MINUS:
        t = octave_value::op_decr;
        break;

      default:
        panic_impossible ();
        break;
      }

    int l = tok_val->line ();
    int c = tok_val->column ();

    return new tree_postfix_expression (op1, l, c, t);
  }

  // Build an unwind-protect command.

  tree_command *
  base_parser::make_unwind_command (token *unwind_tok,
                                    tree_statement_list *body,
                                    tree_statement_list *cleanup_stmts,
                                    token *end_tok,
                                    comment_list *lc,
                                    comment_list *mc)
  {
    tree_command *retval = nullptr;

    if (end_token_ok (end_tok, token::unwind_protect_end))
      {
        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        int l = unwind_tok->line ();
        int c = unwind_tok->column ();

        retval = new tree_unwind_protect_command (body, cleanup_stmts,
                                                  lc, mc, tc, l, c);
      }
    else
      {
        delete body;
        delete cleanup_stmts;

        end_token_error (end_tok, token::unwind_protect_end);
      }

    return retval;
  }

  // Build a try-catch command.

  tree_command *
  base_parser::make_try_command (token *try_tok,
                                 tree_statement_list *body,
                                 char catch_sep,
                                 tree_statement_list *cleanup_stmts,
                                 token *end_tok,
                                 comment_list *lc,
                                 comment_list *mc)
  {
    tree_command *retval = nullptr;

    if (end_token_ok (end_tok, token::try_catch_end))
      {
        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        int l = try_tok->line ();
        int c = try_tok->column ();

        tree_identifier *id = nullptr;

        if (! catch_sep && cleanup_stmts && ! cleanup_stmts->empty ())
          {
            tree_statement *stmt = cleanup_stmts->front ();

            if (stmt)
              {
                tree_expression *expr = stmt->expression ();

                if (expr && expr->is_identifier ())
                  {
                    id = dynamic_cast<tree_identifier *> (expr);

                    cleanup_stmts->pop_front ();

                    stmt->set_expression (nullptr);
                    delete stmt;
                  }
              }
          }

        retval = new tree_try_catch_command (body, cleanup_stmts, id,
                                             lc, mc, tc, l, c);
      }
    else
      {
        delete body;
        delete cleanup_stmts;

        end_token_error (end_tok, token::try_catch_end);
      }

    return retval;
  }

  // Build a while command.

  tree_command *
  base_parser::make_while_command (token *while_tok,
                                   tree_expression *expr,
                                   tree_statement_list *body,
                                   token *end_tok,
                                   comment_list *lc)
  {
    tree_command *retval = nullptr;

    maybe_warn_assign_as_truth_value (expr);

    if (end_token_ok (end_tok, token::while_end))
      {
        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        m_lexer.m_looping--;

        int l = while_tok->line ();
        int c = while_tok->column ();

        retval = new tree_while_command (expr, body, lc, tc, l, c);
      }
    else
      {
        delete expr;
        delete body;

        end_token_error (end_tok, token::while_end);
      }

    return retval;
  }

  // Build a do-until command.

  tree_command *
  base_parser::make_do_until_command (token *until_tok,
                                      tree_statement_list *body,
                                      tree_expression *expr,
                                      comment_list *lc)
  {
    maybe_warn_assign_as_truth_value (expr);

    comment_list *tc = m_lexer.m_comment_buf.get_comment ();

    m_lexer.m_looping--;

    int l = until_tok->line ();
    int c = until_tok->column ();

    return new tree_do_until_command (expr, body, lc, tc, l, c);
  }

  // Build a for command.

  tree_command *
  base_parser::make_for_command (int tok_id, token *for_tok,
                                 tree_argument_list *lhs,
                                 tree_expression *expr,
                                 tree_expression *maxproc,
                                 tree_statement_list *body,
                                 token *end_tok,
                                 comment_list *lc)
  {
    tree_command *retval = nullptr;

    bool parfor = tok_id == PARFOR;

    if (end_token_ok (end_tok, parfor ? token::parfor_end : token::for_end))
      {
        expr->mark_as_for_cmd_expr ();

        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        m_lexer.m_looping--;

        int l = for_tok->line ();
        int c = for_tok->column ();

        if (lhs->length () == 1)
          {
            tree_expression *tmp = lhs->remove_front ();

            m_lexer.mark_as_variable (tmp->name ());

            retval = new tree_simple_for_command (parfor, tmp, expr, maxproc,
                                                  body, lc, tc, l, c);

            delete lhs;
          }
        else
          {
            if (parfor)
              {
                delete lhs;
                delete expr;
                delete maxproc;
                delete body;

                bison_error ("invalid syntax for parfor statement");
              }

            m_lexer.mark_as_variables (lhs->variable_names ());

            retval = new tree_complex_for_command (lhs, expr, body,
                                                   lc, tc, l, c);
          }
      }
    else
      {
        delete lhs;
        delete expr;
        delete maxproc;
        delete body;

        end_token_error (end_tok, parfor ? token::parfor_end : token::for_end);
      }

    return retval;
  }

  // Build a break command.

  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);
  }

  // Build a continue command.

  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);
  }

  // Build a return command.

  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);
  }

  // Build an spmd command.

  tree_spmd_command *
  base_parser::make_spmd_command (token *spmd_tok, tree_statement_list *body,
                                  token *end_tok, comment_list *lc,
                                  comment_list *tc)
  {
    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, lc, tc, l, c);
      }
    else
      {
        delete body;
        delete lc;
        delete tc;

        end_token_error (end_tok, token::spmd_end);
      }

    return retval;
  }

  // Start an if command.

  tree_if_command_list *
  base_parser::start_if_command (tree_expression *expr,
                                 tree_statement_list *list)
  {
    maybe_warn_assign_as_truth_value (expr);

    // Line and column will be set in finish_if_command.

    tree_if_clause *t = new tree_if_clause (expr, list);

    return new tree_if_command_list (t);
  }

  // Finish an if command.

  tree_if_command *
  base_parser::finish_if_command (token *if_tok,
                                  tree_if_command_list *list,
                                  token *end_tok,
                                  comment_list *lc)
  {
    tree_if_command *retval = nullptr;

    if (end_token_ok (end_tok, token::if_end))
      {
        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        int l = if_tok->line ();
        int c = if_tok->column ();

        if (list && ! list->empty ())
          {
            tree_if_clause *elt = list->front ();

            if (elt)
              {
                elt->line (l);
                elt->column (c);
              }
          }

        retval = new tree_if_command (list, lc, tc, l, c);
      }
    else
      {
        delete list;

        end_token_error (end_tok, token::if_end);
      }

    return retval;
  }

  // Build an elseif clause.

  tree_if_clause *
  base_parser::make_elseif_clause (token *elseif_tok,
                                   tree_expression *expr,
                                   tree_statement_list *list,
                                   comment_list *lc)
  {
    maybe_warn_assign_as_truth_value (expr);

    int l = elseif_tok->line ();
    int c = elseif_tok->column ();

    return new tree_if_clause (expr, list, lc, l, c);
  }

  tree_if_clause *
  base_parser::make_else_clause (token *else_tok, comment_list *lc,
                                 tree_statement_list *list)
  {
    int l = else_tok->line ();
    int c = else_tok->column ();

    return new tree_if_clause (list, lc, l, c);
  }

  tree_if_command_list *
  base_parser::append_if_clause (tree_if_command_list *list,
                                 tree_if_clause *clause)
  {
    return list_append (list, clause);
  }

  // Finish a switch command.

  tree_switch_command *
  base_parser::finish_switch_command (token *switch_tok,
                                      tree_expression *expr,
                                      tree_switch_case_list *list,
                                      token *end_tok,
                                      comment_list *lc)
  {
    tree_switch_command *retval = nullptr;

    if (end_token_ok (end_tok, token::switch_end))
      {
        comment_list *tc = m_lexer.m_comment_buf.get_comment ();

        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 (expr, list, lc, tc, l, c);
      }
    else
      {
        delete expr;
        delete list;

        end_token_error (end_tok, token::switch_end);
      }

    return retval;
  }

  tree_switch_case_list *
  base_parser::make_switch_case_list (tree_switch_case *switch_case)
  {
    return new tree_switch_case_list (switch_case);
  }

  // Build a switch case.

  tree_switch_case *
  base_parser::make_switch_case (token *case_tok,
                                 tree_expression *expr,
                                 tree_statement_list *list,
                                 comment_list *lc)
  {
    maybe_warn_variable_switch_label (expr);

    int l = case_tok->line ();
    int c = case_tok->column ();

    return new tree_switch_case (expr, list, lc, l, c);
  }

  tree_switch_case *
  base_parser::make_default_switch_case (token *default_tok, comment_list *lc,
                                         tree_statement_list *list)
  {
    int l = default_tok->line ();
    int c = default_tok->column ();

    return new tree_switch_case (list, lc, l, c);
  }

  tree_switch_case_list *
  base_parser::append_switch_case (tree_switch_case_list *list,
                                   tree_switch_case *elt)
  {
    return list_append (list, elt);
  }

  // Build an assignment to a variable.

  tree_expression *
  base_parser::make_assign_op (int op, tree_argument_list *lhs,
                               token *eq_tok, tree_expression *rhs)
  {
    octave_value::assign_op t = octave_value::unknown_assign_op;

    switch (op)
      {
      case '=':
        t = octave_value::op_asn_eq;
        break;

      case ADD_EQ:
        t = octave_value::op_add_eq;
        break;

      case SUB_EQ:
        t = octave_value::op_sub_eq;
        break;

      case MUL_EQ:
        t = octave_value::op_mul_eq;
        break;

      case DIV_EQ:
        t = octave_value::op_div_eq;
        break;

      case LEFTDIV_EQ:
        t = octave_value::op_ldiv_eq;
        break;

      case POW_EQ:
        t = octave_value::op_pow_eq;
        break;

      case EMUL_EQ:
        t = octave_value::op_el_mul_eq;
        break;

      case EDIV_EQ:
        t = octave_value::op_el_div_eq;
        break;

      case ELEFTDIV_EQ:
        t = octave_value::op_el_ldiv_eq;
        break;

      case EPOW_EQ:
        t = octave_value::op_el_pow_eq;
        break;

      case AND_EQ:
        t = octave_value::op_el_and_eq;
        break;

      case OR_EQ:
        t = octave_value::op_el_or_eq;
        break;

      default:
        panic_impossible ();
        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
        // '=', not '+=', etc.

        delete lhs;
        delete rhs;

        bison_error ("computed multiple assignment not allowed",
                     eq_tok->beg_pos ());

        return nullptr;
      }

    if (lhs->is_simple_assign_lhs ())
      {
        // We are looking at a simple assignment statement like x = rhs;

        tree_expression *tmp = lhs->remove_front ();

        if ((tmp->is_identifier () || tmp->is_index_expression ())
            && iskeyword (tmp->name ()))
          {
            std::string kw = tmp->name ();

            delete tmp;
            delete lhs;
            delete rhs;

            bison_error ("invalid assignment to keyword \"" + kw + "\"",
                         eq_tok->beg_pos ());

            return nullptr;
          }

        delete lhs;

        m_lexer.mark_as_variable (tmp->name ());

        return new tree_simple_assignment (tmp, rhs, false, l, c, t);
      }
    else
      {
        std::list<std::string> names = lhs->variable_names ();

        for (const auto& kw : names)
          {
            if (iskeyword (kw))
              {
                delete lhs;
                delete rhs;

                bison_error ("invalid assignment to keyword \"" + kw + "\"",
                             eq_tok->beg_pos ());

                return nullptr;
              }
          }

        m_lexer.mark_as_variables (names);

        return new tree_multi_assignment (lhs, rhs, false, l, c);
      }
  }

  void
  base_parser::make_script (tree_statement_list *cmds,
                            tree_statement *end_script)
  {
    if (! cmds)
      cmds = new tree_statement_list ();

    cmds->append (end_script);

    symbol_scope script_scope = m_lexer.m_symtab_context.curr_scope ();

    script_scope.cache_name (m_lexer.m_fcn_file_full_name);
    script_scope.cache_fcn_file_name (m_lexer.m_fcn_file_full_name);
    script_scope.cache_dir_name (m_lexer.m_dir_name);

    octave_user_script *script
      = new octave_user_script (m_lexer.m_fcn_file_full_name,
                                m_lexer.m_fcn_file_name, script_scope,
                                cmds, m_lexer.m_help_text);

    m_lexer.m_symtab_context.pop ();
    m_lexer.m_help_text = "";

    sys::time now;

    script->stash_fcn_file_time (now);
    script->stash_dir_name (m_lexer.m_dir_name);

    m_primary_fcn = octave_value (script);
  }

  tree_identifier *
  base_parser::make_fcn_name (tree_identifier *id)
  {
    std::string id_name = id->name ();

    // Make classdef local functions unique from classdef methods.

    if (m_parsing_local_functions && m_curr_fcn_depth == 0)
      id_name = m_lexer.m_fcn_file_name + ">" + id_name;

    if (! m_function_scopes.name_current_scope (id_name))
      {
        bison_error ("duplicate subfunction or nested function name",
                     id->line (), id->column ());

        delete id;
        return nullptr;
      }

    symbol_scope curr_scope = m_lexer.m_symtab_context.curr_scope ();
    curr_scope.cache_name (id_name);

    m_lexer.m_parsed_function_name.top () = true;
    m_lexer.m_maybe_classdef_get_set_method = false;

    return id;
  }

  // Define a function.

  // FIXME: combining start_function, finish_function, and
  // recover_from_parsing_function should be possible, but it makes
  // for a large mess.  Maybe this could be a bit better organized?

  tree_function_def *
  base_parser::make_function (token *fcn_tok,
                              tree_parameter_list *ret_list,
                              tree_identifier *id,
                              tree_parameter_list *param_list,
                              tree_statement_list *body,
                              tree_statement *end_fcn_stmt,
                              comment_list *lc)
  {
    int l = fcn_tok->line ();
    int c = fcn_tok->column ();

    octave_user_function *tmp_fcn
      = start_function (id, param_list, body, end_fcn_stmt);

    tree_function_def *retval = finish_function (ret_list, tmp_fcn, lc, l, c);

    recover_from_parsing_function ();

    return retval;
  }

  // Begin defining a function.

  octave_user_function *
  base_parser::start_function (tree_identifier *id,
                               tree_parameter_list *param_list,
                               tree_statement_list *body,
                               tree_statement *end_fcn_stmt)
  {
    // We'll fill in the return list later.

    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)
      id_name.insert (0, "set.");

    m_lexer.m_parsing_classdef_get_method = false;
    m_lexer.m_parsing_classdef_set_method = false;

    if (! body)
      body = new tree_statement_list ();

    body->append (end_fcn_stmt);

    octave_user_function *fcn
      = new octave_user_function (m_lexer.m_symtab_context.curr_scope (),
                                  param_list, nullptr, body);

    comment_list *tc = m_lexer.m_comment_buf.get_comment ();

    fcn->stash_trailing_comment (tc);
    fcn->stash_fcn_end_location (end_fcn_stmt->line (),
                                 end_fcn_stmt->column ());

    // 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
    // file.  Matlab doesn't provide a diagnostic (it ignores the stated
    // name).
    if (! m_autoloading && m_lexer.m_reading_fcn_file
        && m_curr_fcn_depth == 0 && ! m_parsing_subfunctions)
      {
        // FIXME: should m_lexer.m_fcn_file_name already be
        // preprocessed when we get here?  It seems to only be a
        // problem with relative filenames.

        std::string nm = m_lexer.m_fcn_file_name;

        std::size_t pos = nm.find_last_of (sys::file_ops::dir_sep_chars ());

        if (pos != std::string::npos)
          nm = m_lexer.m_fcn_file_name.substr (pos+1);

        if (nm != id_name)
          {
            warning_with_id
              ("Octave:function-name-clash",
               "function name '%s' does not agree with function filename '%s'",
               id_name.c_str (), m_lexer.m_fcn_file_full_name.c_str ());

            id_name = nm;
          }
      }

    sys::time now;

    fcn->stash_fcn_file_name (m_lexer.m_fcn_file_full_name);
    fcn->stash_fcn_file_time (now);
    fcn->stash_dir_name (m_lexer.m_dir_name);
    fcn->stash_package_name (m_lexer.m_package_name);
    fcn->mark_as_system_fcn_file ();
    fcn->stash_function_name (id_name);

    if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_classdef_file || m_autoloading)
      {
        if (m_fcn_file_from_relative_lookup)
          fcn->mark_relative ();

        if (m_lexer.m_parsing_class_method)
          {
            if (m_lexer.m_parsing_classdef)
              {
                if (m_curr_class_name == id_name)
                  fcn->mark_as_classdef_constructor ();
                else
                  fcn->mark_as_classdef_method ();
              }
            else
              {
                if (m_curr_class_name == id_name)
                  fcn->mark_as_legacy_constructor ();
                else
                  fcn->mark_as_legacy_method ();
              }

            fcn->stash_dispatch_class (m_curr_class_name);
          }

        std::string nm = fcn->fcn_file_name ();

        sys::file_stat fs (nm);

        if (fs && fs.is_newer (now))
          warning_with_id ("Octave:future-time-stamp",
                           "time stamp for '%s' is in the future", nm.c_str ());
      }
    else if (! m_lexer.input_from_tmp_history_file ()
             && ! m_lexer.m_force_script
             && m_lexer.m_reading_script_file
             && m_lexer.m_fcn_file_name == id_name)
      {
        warning ("function '%s' defined within script file '%s'",
                 id_name.c_str (), m_lexer.m_fcn_file_full_name.c_str ());
      }

    // Record help text for functions other than nested functions.
    // We cannot currently record help for nested functions (bug #46008)
    // because the doc_string of the outermost function is read first,
    // whereas this function is called for the innermost function first.
    // We could have a stack of help_text in lexer.
    if (! m_lexer.m_help_text.empty () && m_curr_fcn_depth == 0)
      {
        fcn->document (m_lexer.m_help_text);

        m_lexer.m_help_text = "";
      }

    if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0
        && ! m_parsing_subfunctions)
      m_primary_fcn = octave_value (fcn);

    return fcn;
  }

  tree_statement *
  base_parser::make_end (const std::string& type, bool eof,
                         const filepos& beg_pos, const filepos& /*end_pos*/)
  {
    int l = beg_pos.line ();
    int c = beg_pos.column ();

    return make_statement (new tree_no_op_command (type, eof, l, c));
  }

  tree_function_def *
  base_parser::finish_function (tree_parameter_list *ret_list,
                                octave_user_function *fcn,
                                comment_list *lc,
                                int l, int c)
  {
    tree_function_def *retval = nullptr;

    if (! ret_list)
      ret_list = new tree_parameter_list (tree_parameter_list::out);

    ret_list->mark_as_formal_parameters ();

    if (fcn)
      {
        std::string fcn_nm = fcn->name ();
        std::string file = fcn->fcn_file_name ();

        std::string tmp = fcn_nm;
        if (! file.empty ())
          tmp += ": " + file;

        symbol_scope fcn_scope = fcn->scope ();
        fcn_scope.cache_name (tmp);
        fcn_scope.cache_fcn_name (fcn_nm);
        fcn_scope.cache_fcn_file_name (file);
        fcn_scope.cache_dir_name (m_lexer.m_dir_name);

        if (lc)
          fcn->stash_leading_comment (lc);

        fcn->define_ret_list (ret_list);

        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)
              {
                fcn->mark_as_nested_function ();
                fcn_scope.set_nesting_depth (m_curr_fcn_depth);

                symbol_scope pscope = m_function_scopes.parent_scope ();
                fcn_scope.set_parent (pscope);
                fcn_scope.set_primary_parent (m_primary_fcn_scope);

                pscope.install_nestfunction (fcn_nm, ov_fcn, fcn_scope);

                // For nested functions, the list of parent functions is
                // set in symbol_scope::update_nest.
              }
            else
              {
                fcn->mark_as_subfunction ();
                m_subfunction_names.push_back (fcn_nm);

                fcn_scope.set_parent (m_primary_fcn_scope);
                if (m_parsing_subfunctions)
                  fcn_scope.set_primary_parent (m_primary_fcn_scope);

                m_primary_fcn_scope.install_subfunction (fcn_nm, ov_fcn);
              }
          }

        if (m_curr_fcn_depth == 0)
          fcn_scope.update_nest ();

        if (! m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0)
          {
            // We are either reading a script file or defining a function
            // at the command line, so this definition creates a
            // tree_function object that is placed in the parse tree.
            // Otherwise, it is just inserted in the symbol table,
            // either as a subfunction or nested function (see above),
            // or as the primary function for the file, via
            // m_primary_fcn (see also load_fcn_from_file,,
            // parse_fcn_file, and
            // fcn_info::fcn_info_rep::find_user_function).

            if (m_lexer.m_buffer_function_text)
              {
                fcn->cache_function_text (m_lexer.m_function_text,
                                          fcn->time_parsed ());
                m_lexer.m_buffer_function_text = false;
              }

            retval = new tree_function_def (fcn, l, c);
          }
      }

    return retval;
  }

  tree_statement_list *
  base_parser::append_function_body (tree_statement_list *body,
                                     tree_statement_list *list)
  {
    if (list)
      {
        for (const auto& elt : *list)
          list_append (body, elt);

        list->clear ();
        delete (list);
      }

    return body;
  }

  tree_arguments_block *
  base_parser::make_arguments_block (token *arguments_tok,
                                     tree_args_block_attribute_list *attr_list,
                                     tree_args_block_validation_list *validation_list,
                                     token *end_tok,
                                     comment_list *lc, comment_list *tc)
  {
    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);
      }
    else
      {
        delete attr_list;
        delete validation_list;

        delete lc;
        delete tc;
      }

    return retval;
  }

  tree_arg_validation *
  base_parser::make_arg_validation (tree_arg_size_spec *size_spec,
                                    tree_identifier *class_name,
                                    tree_arg_validation_fcns *validation_fcns,
                                    tree_expression *default_value)
  {
    // FIXME: Validate arguments and convert to more specific types
    // (std::string for arg_name and class_name, etc).

    return new tree_arg_validation (size_spec, class_name,
                                    validation_fcns, default_value);
  }

  tree_args_block_attribute_list *
  base_parser::make_args_attribute_list (tree_identifier *attribute_name)
  {
    // FIXME: Validate argument and convert to more specific type
    // (std::string for attribute_name).

    return new tree_args_block_attribute_list (attribute_name);
  }

  tree_args_block_validation_list *
  base_parser::make_args_validation_list (tree_arg_validation *arg_validation)
  {
    return new tree_args_block_validation_list (arg_validation);
  }

  tree_args_block_validation_list *
  base_parser::append_args_validation_list (tree_args_block_validation_list *list,
                                            tree_arg_validation *arg_validation)
  {
    return list_append (list, arg_validation);
  }

  tree_arg_size_spec *
  base_parser::make_arg_size_spec (tree_argument_list *size_args)
  {
    // FIXME: Validate argument.

    return new tree_arg_size_spec (size_args);
  }

  tree_arg_validation_fcns *
  base_parser::make_arg_validation_fcns (tree_argument_list *fcn_args)
  {
    // FIXME: Validate argument.

    return new tree_arg_validation_fcns (fcn_args);
  }

  void
  base_parser::recover_from_parsing_function (void)
  {
    m_lexer.m_symtab_context.pop ();

    if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0
        && ! m_parsing_subfunctions)
      m_parsing_subfunctions = true;

    m_curr_fcn_depth--;
    m_function_scopes.pop ();

    m_lexer.m_defining_func--;
    m_lexer.m_parsed_function_name.pop ();
    m_lexer.m_looking_at_return_list = false;
    m_lexer.m_looking_at_parameter_list = false;
  }

  // A CLASSDEF block defines a class that has a constructor and other
  // methods, but it is not an executable command.  Parsing the block
  // makes some changes in the symbol table (inserting the constructor
  // and methods, and adding to the list of known objects) and creates
  // a parse tree containing meta information about the class.

  // LC contains comments appearing before the classdef keyword.
  // TC contains comments appearing between the classdef elements
  // and the final end token for the classdef block.

  tree_classdef *
  base_parser::make_classdef (token *tok_val,
                              tree_classdef_attribute_list *a,
                              tree_identifier *id,
                              tree_classdef_superclass_list *sc,
                              tree_classdef_body *body, token *end_tok,
                              comment_list *lc, comment_list *tc)
  {
    tree_classdef *retval = nullptr;

    m_lexer.m_symtab_context.pop ();

    std::string cls_name = id->name ();

    std::string full_name = m_lexer.m_fcn_file_full_name;
    std::string short_name = m_lexer.m_fcn_file_name;

    std::size_t pos
      = short_name.find_last_of (sys::file_ops::dir_sep_chars ());

    if (pos != std::string::npos)
      short_name = short_name.substr (pos+1);

    if (short_name != cls_name)
      {
        int l = id->line ();
        int c = id->column ();

        delete a;
        delete id;
        delete sc;
        delete body;
        delete lc;
        delete tc;

        bison_error ("invalid classdef definition, the class name must match the filename", l, c);

      }
    else
      {
        if (end_token_ok (end_tok, token::classdef_end))
          {
            int l = tok_val->line ();
            int c = tok_val->column ();

            if (! body)
              body = new tree_classdef_body ();

            retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (),
                                        a, id, sc, body, lc, tc,
                                        m_curr_package_name, full_name, l, c);
          }
        else
          {
            delete a;
            delete id;
            delete sc;
            delete body;
            delete lc;
            delete tc;

            end_token_error (end_tok, token::switch_end);
          }
      }

    return retval;
  }

  // LC contains comments appearing before the properties keyword.
  // If this properties block appears first in the list of classdef
  // elements, this comment list will be used for the help text for the
  // classdef block.

  // TC contains comments appearing between the list of properties
  // and the final end token for the properties block and may be used to
  // find the doc string for the final property in the list.

  tree_classdef_properties_block *
  base_parser::make_classdef_properties_block (token *tok_val,
                                               tree_classdef_attribute_list *a,
                                               tree_classdef_property_list *plist,
                                               token *end_tok,
                                               comment_list *lc,
                                               comment_list *tc)
  {
    tree_classdef_properties_block *retval = nullptr;

    if (end_token_ok (end_tok, token::properties_end))
      {
        int l = tok_val->line ();
        int c = tok_val->column ();

        if (plist)
          {
            // If the element at the end of the list doesn't have a doc
            // string, see whether the first element of TC is an
            // end-of-line comment for us to use.

            if (tc)
              {
                tree_classdef_property *last_elt = plist->back ();

                if (! last_elt->have_doc_string ())
                  {
                    comment_elt first_comment_elt = tc->front ();

                    if (first_comment_elt.is_end_of_line ())
                      {
                        std::string eol_comment = first_comment_elt.text ();

                        last_elt->doc_string (eol_comment);
                      }
                  }
              }
          }
        else
          plist = new tree_classdef_property_list ();

        retval = new tree_classdef_properties_block (a, plist, lc, tc, l, c);
      }
    else
      {
        delete a;
        delete plist;
        delete lc;
        delete tc;

        end_token_error (end_tok, token::properties_end);
      }

    return retval;
  }

  tree_classdef_property_list *
  base_parser::make_classdef_property_list (tree_classdef_property *prop)
  {
    return new tree_classdef_property_list (prop);
  }

  tree_classdef_property *
  base_parser::make_classdef_property (comment_list *lc, tree_identifier *id,
                                       tree_arg_validation *av)
  {
    av->arg_name (id);

    if (av->size_spec () || av->class_name () || av->validation_fcns ())
      warning ("size, class, and validation function specifications are not yet supported for classdef properties; INCORRECT RESULTS ARE POSSIBLE!");

    return new tree_classdef_property (av, lc);
  }

  // LC contains comments appearing before the methods keyword.
  // If this methods block appears first in the list of classdef
  // elements, this comment list will be used for the help text for the
  // classdef block.

  tree_classdef_methods_block *
  base_parser::make_classdef_methods_block (token *tok_val,
                                            tree_classdef_attribute_list *a,
                                            tree_classdef_methods_list *mlist,
                                            token *end_tok, comment_list *lc,
                                            comment_list *tc)
  {
    tree_classdef_methods_block *retval = nullptr;

    if (end_token_ok (end_tok, token::methods_end))
      {
        int l = tok_val->line ();
        int c = tok_val->column ();

        if (! mlist)
          mlist = new tree_classdef_methods_list ();

        retval = new tree_classdef_methods_block (a, mlist, lc, tc, l, c);
      }
    else
      {
        delete a;
        delete mlist;
        delete lc;
        delete tc;

        end_token_error (end_tok, token::methods_end);
      }

    return retval;
  }

  // LC contains comments appearing before the events keyword.
  // If this events block appears first in the list of classdef
  // elements, this comment list will be used for the help text for the
  // classdef block.

  // TC contains comments appearing between the list of events and
  // the final end token for the events block and may be used to find
  // the doc string for the final event in the list.

  tree_classdef_events_block *
  base_parser::make_classdef_events_block (token *tok_val,
                                           tree_classdef_attribute_list *a,
                                           tree_classdef_events_list *elist,
                                           token *end_tok,
                                           comment_list *lc,
                                           comment_list *tc)
  {
    tree_classdef_events_block *retval = nullptr;

    if (end_token_ok (end_tok, token::events_end))
      {
        int l = tok_val->line ();
        int c = tok_val->column ();

        if (! elist)
          elist = new tree_classdef_events_list ();

        retval = new tree_classdef_events_block (a, elist, lc, tc, l, c);
      }
    else
      {
        delete a;
        delete elist;
        delete lc;
        delete tc;

        end_token_error (end_tok, token::events_end);
      }

    return retval;
  }

  tree_classdef_events_list *
  base_parser::make_classdef_events_list (tree_classdef_event *e)
  {
    return new tree_classdef_events_list (e);
  }

  tree_classdef_event *
  base_parser::make_classdef_event (comment_list *lc, tree_identifier *id)
  {
    return new tree_classdef_event (id, lc);
  }

  // LC contains comments appearing before the enumeration keyword.
  // If this enumeration block appears first in the list of classdef
  // elements, this comment list will be used for the help text for the
  // classdef block.

  // TC contains comments appearing between the list of
  // enumerations and the final end token for the enumeration block and
  // may be used to find the doc string for the final enumeration in the
  // list.

  tree_classdef_enum_block *
  base_parser::make_classdef_enum_block (token *tok_val,
                                         tree_classdef_attribute_list *a,
                                         tree_classdef_enum_list *elist,
                                         token *end_tok,
                                         comment_list *lc,
                                         comment_list *tc)
  {
    tree_classdef_enum_block *retval = nullptr;

    if (end_token_ok (end_tok, token::enumeration_end))
      {
        int l = tok_val->line ();
        int c = tok_val->column ();

        if (! elist)
          elist = new tree_classdef_enum_list ();

        retval = new tree_classdef_enum_block (a, elist, lc, tc, l, c);
      }
    else
      {
        delete a;
        delete elist;
        delete lc;
        delete tc;

        end_token_error (end_tok, token::enumeration_end);
      }

    return retval;
  }

  tree_classdef_enum_list *
  base_parser::make_classdef_enum_list (tree_classdef_enum *e)
  {
    return new tree_classdef_enum_list (e);
  }

  tree_classdef_enum *
  base_parser::make_classdef_enum (tree_identifier *id, tree_expression *expr,
                                   comment_list *lc)
  {
    return new tree_classdef_enum (id, expr, lc);
  }

  tree_classdef_property_list *
  base_parser::append_classdef_property (tree_classdef_property_list *list,
                                         tree_classdef_property *elt)
  {
    return list_append (list, elt);
  }

  tree_classdef_events_list *
  base_parser::append_classdef_event (tree_classdef_events_list *list,
                                      tree_classdef_event *elt)
  {
    return list_append (list, elt);
  }

  tree_classdef_enum_list *
  base_parser::append_classdef_enum (tree_classdef_enum_list *list,
                                     tree_classdef_enum *elt)
  {
    return list_append (list, elt);
  }

  tree_classdef_superclass_list *
  base_parser::make_classdef_superclass_list (tree_classdef_superclass *sc)
  {
    return new tree_classdef_superclass_list (sc);
  }

  tree_classdef_superclass *
  base_parser::make_classdef_superclass (token *fqident)
  {
    return new tree_classdef_superclass (fqident->text ());
  }

  tree_classdef_superclass_list *
  base_parser::append_classdef_superclass (tree_classdef_superclass_list *list,
                                           tree_classdef_superclass *elt)
  {
    return list_append (list, elt);
  }

  tree_classdef_attribute_list *
  base_parser::make_classdef_attribute_list (tree_classdef_attribute *attr)
  {
    return new tree_classdef_attribute_list (attr);
  }

  tree_classdef_attribute *
  base_parser::make_classdef_attribute (tree_identifier *id,
                                        tree_expression *expr)
  {
    return (expr
            ? new tree_classdef_attribute (id, expr)
            : new tree_classdef_attribute (id));
  }

  tree_classdef_attribute *
  base_parser::make_not_classdef_attribute (tree_identifier *id)
  {
    return new tree_classdef_attribute (id, false);
  }

  tree_classdef_attribute_list *
  base_parser::append_classdef_attribute (tree_classdef_attribute_list *list,
                                          tree_classdef_attribute *elt)
  {
    return list_append (list, elt);
  }

  tree_classdef_body *
  base_parser::make_classdef_body (tree_classdef_properties_block *pb)
  {
    return new tree_classdef_body (pb);
  }

  tree_classdef_body *
  base_parser::make_classdef_body (tree_classdef_methods_block *mb)
  {
    return new tree_classdef_body (mb);
  }

  tree_classdef_body *
  base_parser::make_classdef_body (tree_classdef_events_block *evb)
  {
    return new tree_classdef_body (evb);
  }

  tree_classdef_body *
  base_parser::make_classdef_body  (tree_classdef_enum_block *enb)
  {
    return new tree_classdef_body (enb);
  }

  tree_classdef_body *
  base_parser::append_classdef_properties_block (tree_classdef_body *body,
                                                 tree_classdef_properties_block *block)
  {
    return list_append (body, block);
  }

  tree_classdef_body *
  base_parser::append_classdef_methods_block (tree_classdef_body *body,
                                              tree_classdef_methods_block *block)
  {
    return list_append (body, block);
  }

  tree_classdef_body *
  base_parser::append_classdef_events_block (tree_classdef_body *body,
                                             tree_classdef_events_block *block)
  {
    return list_append (body, block);
  }

  tree_classdef_body *
  base_parser::append_classdef_enum_block (tree_classdef_body *body,
                                           tree_classdef_enum_block *block)
  {
    return list_append (body, block);
  }

  octave_user_function*
  base_parser::start_classdef_external_method (tree_identifier *id,
                                               tree_parameter_list *pl)
  {
    octave_user_function* retval = nullptr;

    // External methods are only allowed within @-folders. In this case,
    // m_curr_class_name will be non-empty.

    if (! m_curr_class_name.empty ())
      {

        std::string mname = id->name ();

        // Methods that cannot be declared outside the classdef file:
        // - methods with '.' character (e.g. property accessors)
        // - class constructor
        // - 'delete'

        if (mname.find_first_of (".") == std::string::npos
            && mname != "delete"
            && mname != m_curr_class_name)
          {
            // Create a dummy function that is used until the real method
            // is loaded.

            retval = new octave_user_function (symbol_scope (), 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 "
                       "method cannot be the class constructor, 'delete' "
                       "or have a dot (.) character in its name");
      }
    else
      bison_error ("external methods are only allowed in @-folders");

    if (! retval)
      delete id;

    return retval;
  }

  tree_function_def *
  base_parser::finish_classdef_external_method (octave_user_function *fcn,
                                                tree_parameter_list *ret_list,
                                                comment_list *cl)
  {
    if (! ret_list)
      ret_list = new tree_parameter_list (tree_parameter_list::out);

    fcn->define_ret_list (ret_list);

    if (cl)
      fcn->stash_leading_comment (cl);

    int l = fcn->beginning_line ();
    int c = fcn->beginning_column ();

    return new tree_function_def (fcn, l, c);
  }

  tree_classdef_methods_list *
  base_parser::make_classdef_methods_list (tree_function_def *fcn_def)
  {
    octave_value fcn;

    if (fcn_def)
      fcn = fcn_def->function ();

    delete fcn_def;

    return new tree_classdef_methods_list (fcn);
  }

  tree_classdef_methods_list *
  base_parser::append_classdef_method (tree_classdef_methods_list *list,
                                       tree_function_def *fcn_def)
  {
    octave_value fcn;

    if (fcn_def)
      {
        fcn = fcn_def->function ();

        delete fcn_def;
      }

    return list_append (list, fcn);
  }

  bool
  base_parser::finish_classdef_file (tree_classdef *cls,
                                     tree_statement_list *local_fcns)
  {
    parse_tree_validator validator;

    cls->accept (validator);

    if (local_fcns)
      {
        for (tree_statement *elt : *local_fcns)
          {
            tree_command *cmd = elt->command ();

            tree_function_def *fcn_def
              = dynamic_cast<tree_function_def *> (cmd);

            fcn_def->accept (validator);
          }
      }

    if (! validator.ok ())
      {
        delete cls;
        delete local_fcns;

        bison_error (validator.error_list ());

        return false;
      }

    // Require all validations to succeed before installing any local
    // functions or defining the classdef object for later use.

    if (local_fcns)
      {
        symbol_table& symtab
          = __get_symbol_table__ ("base_parser::finish_classdef_file");

        for (tree_statement *elt : *local_fcns)
          {
            tree_command *cmd = elt->command ();

            tree_function_def *fcn_def
              = dynamic_cast<tree_function_def *> (cmd);

            octave_value ov_fcn = fcn_def->function ();
            octave_user_function *fcn = ov_fcn.user_function_value ();

            std::string nm = fcn->name ();
            std::string file = fcn->fcn_file_name ();

            symtab.install_local_function (nm, ov_fcn, file);
          }

        delete local_fcns;
      }

    // FIXME: Is it possible for the following condition to be false?
    if (m_lexer.m_reading_classdef_file)
      m_classdef_object = std::shared_ptr<tree_classdef> (cls);

    return true;
  }

  // Make an index expression.

  tree_index_expression *
  base_parser::make_index_expression (tree_expression *expr,
                                      tree_argument_list *args,
                                      char type)
  {
    tree_index_expression *retval = nullptr;

    if (args && args->has_magic_tilde ())
      {
        delete expr;
        delete args;

        bison_error ("invalid use of empty argument (~) in index expression");
      }
    else
      {
        int l = expr->line ();
        int c = expr->column ();

        if (! expr->is_postfix_indexed ())
          expr->set_postfix_index (type);

        if (expr->is_index_expression ())
          {
            tree_index_expression *tmp
              = dynamic_cast<tree_index_expression *> (expr);

            retval = tmp->append (args, type);
          }
        else
          retval = new tree_index_expression (expr, args, l, c, type);
      }

    return retval;
  }

  // Make an indirect reference expression.

  tree_index_expression *
  base_parser::make_indirect_ref (tree_expression *expr,
                                  const std::string& elt)
  {
    tree_index_expression *retval = nullptr;

    int l = expr->line ();
    int c = expr->column ();

    if (! expr->is_postfix_indexed ())
      expr->set_postfix_index ('.');

    if (expr->is_index_expression ())
      {
        tree_index_expression *tmp
          = dynamic_cast<tree_index_expression *> (expr);

        retval = tmp->append (elt);
      }
    else
      retval = new tree_index_expression (expr, elt, l, c);

    m_lexer.m_looking_at_indirect_ref = false;

    return retval;
  }

  // Make an indirect reference expression with dynamic field name.

  tree_index_expression *
  base_parser::make_indirect_ref (tree_expression *expr,
                                  tree_expression *elt)
  {
    tree_index_expression *retval = nullptr;

    int l = expr->line ();
    int c = expr->column ();

    if (! expr->is_postfix_indexed ())
      expr->set_postfix_index ('.');

    if (expr->is_index_expression ())
      {
        tree_index_expression *tmp
          = dynamic_cast<tree_index_expression *> (expr);

        retval = list_append (tmp, elt);
      }
    else
      retval = new tree_index_expression (expr, elt, l, c);

    m_lexer.m_looking_at_indirect_ref = false;

    return retval;
  }

  // Make a declaration command.

  tree_decl_command *
  base_parser::make_decl_command (int tok, token *tok_val,
                                  tree_decl_init_list *lst)
  {
    tree_decl_command *retval = nullptr;

    int l = tok_val->line ();
    int c = tok_val->column ();

    if (lst)
      m_lexer.mark_as_variables (lst->variable_names ());

    switch (tok)
      {
      case GLOBAL:
        {
          retval = new tree_decl_command ("global", lst, l, c);
          retval->mark_global ();
        }
        break;

      case PERSISTENT:
        if (m_curr_fcn_depth >= 0)
          {
            retval = new tree_decl_command ("persistent", lst, l, c);
            retval->mark_persistent ();
          }
        else
          {
            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 ());
            else
              warning ("ignoring persistent declaration near line %d", l);
          }
        break;

      default:
        panic_impossible ();
        break;
      }

    return retval;
  }

  tree_decl_init_list *
  base_parser::make_decl_init_list (tree_decl_elt *elt)
  {
    return new tree_decl_init_list (elt);
  }

  tree_decl_init_list *
  base_parser::append_decl_init_list (tree_decl_init_list *list,
                                      tree_decl_elt *elt)
  {
    return list_append (list, elt);
  }

  tree_decl_elt *
  base_parser::make_decl_elt (tree_identifier *id, token */*eq_op*/,
                              tree_expression *expr)
  {
    return expr ? new tree_decl_elt (id, expr) : new tree_decl_elt (id);
  }

  bool
  base_parser::validate_param_list (tree_parameter_list *lst,
                                    tree_parameter_list::in_or_out type)
  {
    std::set<std::string> dict;

    for (tree_decl_elt *elt : *lst)
      {
        tree_identifier *id = elt->ident ();

        if (id)
          {
            std::string name = id->name ();

            if (id->is_black_hole ())
              {
                if (type != tree_parameter_list::in)
                  {
                    bison_error ("invalid use of ~ in output list");
                    return false;
                  }
              }
            else if (iskeyword (name))
              {
                bison_error ("invalid use of keyword '" + name
                             + "' in parameter list");
                return false;
              }
            else if (dict.find (name) != dict.end ())
              {
                bison_error ("'" + name
                             + "' appears more than once in parameter list");
                return false;
              }
            else
              dict.insert (name);
          }
      }

    std::string va_type = (type == tree_parameter_list::in
                           ? "varargin" : "varargout");

    std::size_t len = lst->length ();

    if (len > 0)
      {
        tree_decl_elt *elt = lst->back ();

        tree_identifier *id = elt->ident ();

        if (id && id->name () == va_type)
          {
            if (len == 1)
              lst->mark_varargs_only ();
            else
              lst->mark_varargs ();

            tree_parameter_list::iterator p = lst->end ();
            --p;
            delete *p;
            lst->erase (p);
          }
      }

    return true;
  }

  bool
  base_parser::validate_array_list (tree_expression *e)
  {
    bool retval = true;

    tree_array_list *al = dynamic_cast<tree_array_list *> (e);

    for (tree_argument_list* row : *al)
      {
        if (row && row->has_magic_tilde ())
          {
            retval = false;

            if (e->is_matrix ())
              bison_error ("invalid use of tilde (~) in matrix expression");
            else
              bison_error ("invalid use of tilde (~) in cell expression");

            break;
          }
      }

    return retval;
  }

  tree_argument_list *
  base_parser::validate_matrix_for_assignment (tree_expression *e)
  {
    tree_argument_list *retval = nullptr;

    if (e->is_constant ())
      {
        tree_evaluator& tw
          = __get_evaluator__ ("validate_matrix_for_assignment");

        octave_value ov = e->evaluate (tw);

        delete e;

        if (ov.isempty ())
          bison_error ("invalid empty left hand side of assignment");
        else
          bison_error ("invalid constant left hand side of assignment");
      }
    else
      {
        bool is_simple_assign = true;

        tree_argument_list *tmp = nullptr;

        if (e->is_matrix ())
          {
            tree_matrix *mat = dynamic_cast<tree_matrix *> (e);

            if (mat && mat->size () == 1)
              {
                tmp = mat->front ();
                mat->pop_front ();
                delete e;
                is_simple_assign = false;
              }
          }
        else
          tmp = new tree_argument_list (e);

        if (tmp && tmp->is_valid_lvalue_list ())
          {
            m_lexer.mark_as_variables (tmp->variable_names ());
            retval = tmp;
          }
        else
          {
            delete tmp;

            bison_error ("invalid left hand side of assignment");
          }

        if (retval && is_simple_assign)
          retval->mark_as_simple_assign_lhs ();
      }

    return retval;
  }

  // Finish building an array_list.

  tree_expression *
  base_parser::finish_array_list (tree_array_list *array_list,
                                  token */*open_delim*/, token *close_delim)
  {
    tree_expression *retval = array_list;

    array_list->set_location (close_delim->line (), close_delim->column ());

    if (array_list->all_elements_are_constant ())
      {
        interpreter& interp = __get_interpreter__ ("finish_array_list");

        try
          {
            // If the evaluation generates a warning message, restore
            // the previous value of last_warning_message and skip the
            // conversion to a constant value.

            error_system& es = interp.get_error_system ();

            unwind_action restore_last_warning_message
              (&error_system::set_last_warning_message, &es,
               es.last_warning_message (""));

            unwind_action restore_discard_warning_messages
              (&error_system::set_discard_warning_messages, &es,
               es.discard_warning_messages (true));

            tree_evaluator& tw = interp.get_evaluator ();

            octave_value tmp = array_list->evaluate (tw);

            std::string msg = es.last_warning_message ();

            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 ());

                delete array_list;

                retval = tc_retval;
              }
          }
        catch (const execution_exception&)
          {
            interp.recover_from_exception ();
          }
      }

    return retval;
  }

  // Finish building a matrix list.

  tree_expression *
  base_parser::finish_matrix (tree_matrix *m, token *open_delim,
                              token *close_delim)
  {
    return (m
            ? finish_array_list (m, open_delim, close_delim)
            : new tree_constant (octave_null_matrix::instance,
                                 close_delim->line (), close_delim->column ()));
  }

  tree_matrix *
  base_parser::make_matrix (tree_argument_list *row)
  {
    return row ? new tree_matrix (row) : nullptr;
  }

  tree_matrix *
  base_parser::append_matrix_row (tree_matrix *matrix, tree_argument_list *row)
  {
    if (! matrix)
      return make_matrix (row);

    return row ? list_append (matrix, row) : matrix;
  }

  // Finish building a cell list.

  tree_expression *
  base_parser::finish_cell (tree_cell *c, token *open_delim,
                            token *close_delim)
  {
    return (c
            ? finish_array_list (c, open_delim, close_delim)
            : new tree_constant (octave_value (Cell ()),
                                 close_delim->line (), close_delim->column ()));
  }

  tree_cell *
  base_parser::make_cell (tree_argument_list *row)
  {
    return row ? new tree_cell (row) : nullptr;
  }

  tree_cell *
  base_parser::append_cell_row (tree_cell *cell, tree_argument_list *row)
  {
    if (! cell)
      return make_cell (row);

    return row ? list_append (cell, row) : cell;
  }

  tree_identifier *
  base_parser::make_identifier (token *ident)
  {
    // Find the token in the symbol table.
    symbol_scope scope = m_lexer.m_symtab_context.curr_scope ();

    std::string nm = ident->text ();

    symbol_record sr = (scope ? scope.insert (nm) : symbol_record (nm));


    int l = ident->line ();
    int c = ident->column ();

    return new tree_identifier (sr, l, c);
  }

  tree_superclass_ref *
  base_parser::make_superclass_ref (token *superclassref)
  {
    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);
  }

  tree_metaclass_query *
  base_parser::make_metaclass_query (token *metaquery)
  {
    std::string cls = metaquery->text ();

    int l = metaquery->line ();
    int c = metaquery->column ();

    return new tree_metaclass_query (cls, l, c);
  }

  tree_statement_list *
  base_parser::set_stmt_print_flag (tree_statement_list *list,
                                    char sep, bool warn_missing_semi)
  {
    tree_statement *tmp = list->back ();

    switch (sep)
      {
      case ';':
        tmp->set_print_flag (false);
        break;

      case 0:
      case ',':
      case '\n':
        tmp->set_print_flag (true);
        if (warn_missing_semi)
          maybe_warn_missing_semi (list);
        break;

      default:
        warning ("unrecognized separator type!");
        break;
      }

    // Even if a statement is null, we add it to the list then remove it
    // here so that the print flag is applied to the correct statement.

    if (tmp->is_null_statement ())
      {
        list->pop_back ();
        delete tmp;
      }

    return list;
  }

  // Finish building a statement.
  template <typename T>
  tree_statement *
  base_parser::make_statement (T *arg)
  {
    comment_list *comment = m_lexer.get_comment ();

    return new tree_statement (arg, comment);
  }

  tree_statement_list *
  base_parser::make_statement_list (tree_statement *stmt)
  {
    return new tree_statement_list (stmt);
  }

  tree_statement_list *
  base_parser::append_statement_list (tree_statement_list *list,
                                      char sep, tree_statement *stmt,
                                      bool warn_missing_semi)
  {
    set_stmt_print_flag (list, sep, warn_missing_semi);

    return list_append (list, stmt);
  }

  tree_argument_list *
  base_parser::make_argument_list (tree_expression *expr)
  {
    return new tree_argument_list (expr);
  }

  tree_argument_list *
  base_parser::append_argument_list (tree_argument_list *list,
                                     tree_expression *expr)
  {
    return list_append (list, expr);
  }

  tree_parameter_list *
  base_parser::make_parameter_list (tree_parameter_list::in_or_out io)
  {
    return new tree_parameter_list (io);
  }

  tree_parameter_list *
  base_parser::make_parameter_list (tree_parameter_list::in_or_out io,
                                    tree_decl_elt *t)
  {
    return new tree_parameter_list (io, t);
  }

  tree_parameter_list *
  base_parser::make_parameter_list (tree_parameter_list::in_or_out io,
                                    tree_identifier *id)
  {
    return new tree_parameter_list (io, id);
  }

  tree_parameter_list *
  base_parser::append_parameter_list (tree_parameter_list *list,
                                      tree_decl_elt *t)
  {
    return list_append (list, t);
  }

  tree_parameter_list *
  base_parser::append_parameter_list (tree_parameter_list *list,
                                      tree_identifier *id)
  {
    return list_append (list, new tree_decl_elt (id));
  }

  void
  base_parser::disallow_command_syntax (void)
  {
    m_lexer.m_allow_command_syntax = false;
  }

  // FIXME: this function partially duplicates do_dbtype in debug.cc.
  static std::string
  get_file_line (const std::string& name, int line)
  {
    // NAME should be an absolute file name and the file should exist.

    std::ifstream fs = sys::ifstream (name.c_str (), std::ios::in);

    std::string text;

    if (fs)
      {
        int i = 1;

        do
          {
            if (! std::getline (fs, text))
              {
                text = "";
                break;
              }
          }
        while (i++ < line);
      }

    return text;
  }

  void
  base_parser::bison_error (const std::string& str)
  {
    bison_error (str, m_lexer.m_filepos);
  }

  void
  base_parser::bison_error (const std::string& str, const filepos& pos)
  {
    bison_error (str, pos.line (), pos.column ());
  }

  void
  base_parser::bison_error (const std::string& str, int err_line, int err_col)
  {
    std::ostringstream output_buf;

    if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_script_file
        || m_lexer.m_reading_classdef_file)
      output_buf << "parse error near line " << err_line
                 << " of file " << m_lexer.m_fcn_file_full_name;
    else
      output_buf << "parse error:";

    if (str != "parse error")
      output_buf << "\n\n  " << str;

    output_buf << "\n\n";

    std::string curr_line;

    if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_script_file
        || m_lexer.m_reading_classdef_file)
      curr_line = get_file_line (m_lexer.m_fcn_file_full_name, err_line);
    else
      curr_line = m_lexer.m_current_input_line;

    // Adjust the error column for display because it is 1-based in the
    // lexer for easier reporting.
    err_col--;

    if (! curr_line.empty ())
      {
        // FIXME: we could do better if we just cached lines from the
        // input file in a list.  See also functions for managing input
        // buffers in lex.ll.

        std::size_t len = curr_line.length ();

        if (curr_line[len-1] == '\n')
          curr_line.resize (len-1);

        // Print the line, maybe with a pointer near the error token.

        output_buf << ">>> " << curr_line << "\n";

        if (err_col == 0)
          err_col = len;

        for (int i = 0; i < err_col + 3; i++)
          output_buf << " ";

        output_buf << "^";
      }

    output_buf << "\n";

    m_parse_error_msg = output_buf.str ();
  }

  void
  base_parser::bison_error (const parse_exception& pe)
  {
    bison_error (pe.message (), pe.line (), pe.column ());
  }

  void
  base_parser::bison_error (const std::list<parse_exception>& pe_list)
  {
    // For now, we just report the first error found.  Reporting all
    // errors will require a bit more refactoring.

    parse_exception pe = pe_list.front ();

    bison_error (pe.message (), pe.line (), pe.column ());
  }

  int
  parser::run (void)
  {
    int status = -1;

    yypstate *pstate = static_cast<yypstate *> (m_parser_state);

    try
      {
        status = octave_pull_parse (pstate, *this);
      }
    catch (const execution_exception&)
      {
        // FIXME: In previous versions, we emitted a parse error here
        // but that is not always correct because the error could have
        // happened inside a GUI callback functions executing in the
        // readline event_hook loop.  Maybe we need a separate exception
        // class for parse errors?

        throw;
      }
    catch (const exit_exception&)
      {
        throw;
      }
    catch (const interrupt_exception&)
      {
        throw;
      }
    catch (...)
      {
        std::string file = m_lexer.m_fcn_file_full_name;

        if (file.empty ())
          error ("unexpected exception while parsing input");
        else
          error ("unexpected exception while parsing %s", file.c_str ());
      }

    if (status != 0)
      parse_error ("%s", m_parse_error_msg.c_str ());

    return status;
  }

  // Parse input from INPUT.  Pass TRUE for EOF if the end of INPUT should
  // finish the parse.

  int
  push_parser::run (const std::string& input, bool eof)
  {
    int status = -1;

    dynamic_cast<push_lexer&> (m_lexer).append_input (input, eof);

    do
      {
        YYSTYPE lval;

        int token = octave_lex (&lval, m_lexer.m_scanner);

        if (token < 0)
          {
            // TOKEN == -2 means that the lexer recognized a comment
            // and we should be at the end of the buffer but not the
            // end of the file so we should return 0 to indicate
            // "complete input" instead of -1 to request more input.

            status = (token == -2 ? 0 : -1);

            if (! eof && m_lexer.at_end_of_buffer ())
              return status;

            break;
          }

        yypstate *pstate = static_cast<yypstate *> (m_parser_state);

        try
          {
            status = octave_push_parse (pstate, token, &lval, *this);
          }
        catch (execution_exception& e)
          {
            std::string file = m_lexer.m_fcn_file_full_name;

            if (file.empty ())
              error (e, "parse error");
            else
              error (e, "parse error in %s", file.c_str ());
          }
        catch (const exit_exception&)
          {
            throw;
          }
        catch (interrupt_exception &)
          {
            throw;
          }
        catch (...)
          {
            std::string file = m_lexer.m_fcn_file_full_name;

            if (file.empty ())
              error ("unexpected exception while parsing input");
            else
              error ("unexpected exception while parsing %s", file.c_str ());
          }
      }
    while (status == YYPUSH_MORE || ! m_lexer.at_end_of_buffer ());

    if (status != 0)
      parse_error ("%s", m_parse_error_msg.c_str ());

    return status;
  }

  int
  push_parser::run (void)
  {
    if (! m_reader)
      error ("push_parser::run requires valid input_reader");

    int exit_status = 0;

    input_system&  input_sys = m_interpreter.get_input_system ();

    std::string prompt
      = command_editor::decode_prompt_string (input_sys.PS1 ());

    do
      {
        // Reset status each time through the read loop so that
        // it won't be set to -1 and cause us to exit the outer
        // loop early if there is an exception while reading
        // input or parsing.

        exit_status = 0;

        bool eof = false;
        std::string input_line = m_reader->get_input (prompt, eof);

        if (eof)
          {
            exit_status = EOF;
            break;
          }

        exit_status = run (input_line, false);

        prompt = command_editor::decode_prompt_string (input_sys.PS2 ());
      }
    while (exit_status < 0);

    return exit_status;
  }

  octave_value
  parse_fcn_file (interpreter& interp, const std::string& full_file,
                  const std::string& file, const std::string& dir_name,
                  const std::string& dispatch_type,
                  const std::string& package_name, bool require_file,
                  bool force_script, bool autoload, bool relative_lookup)
  {
    octave_value retval;

    FILE *ffile = nullptr;

    if (! full_file.empty ())
    {
      // Check that m-file is not overly large which can segfault interpreter.
      const int max_file_size = 512 * 1024 * 1024;  // 512 MB
      sys::file_stat fs (full_file);

      if (fs && fs.size () > max_file_size)
        {
          error ("file '%s' is too large, > 512 MB", full_file.c_str ());

          return octave_value ();
        }

      ffile = sys::fopen (full_file, "rb");
    }

    if (! ffile)
      {
        if (require_file)
          error ("no such file, '%s'", full_file.c_str ());

        return octave_value ();
      }

    unwind_action act ([=] (void) { ::fclose (ffile); });

    // get the encoding for this folder
    input_system& input_sys = interp.get_input_system ();
    parser parser (ffile, interp, input_sys.dir_encoding (dir_name));

    parser.m_curr_class_name = dispatch_type;
    parser.m_curr_package_name = package_name;
    parser.m_autoloading = autoload;
    parser.m_fcn_file_from_relative_lookup = relative_lookup;

    parser.m_lexer.m_force_script = force_script;
    parser.m_lexer.prep_for_file ();
    parser.m_lexer.m_parsing_class_method = ! dispatch_type.empty ();

    parser.m_lexer.m_fcn_file_name = file;
    parser.m_lexer.m_fcn_file_full_name = full_file;
    parser.m_lexer.m_dir_name = dir_name;
    parser.m_lexer.m_package_name = package_name;

    int err = parser.run ();

    if (err)
      error ("parse error while reading file %s", full_file.c_str ());

    octave_value ov_fcn = parser.m_primary_fcn;

    if (parser.m_lexer.m_reading_classdef_file
        && parser.classdef_object ())
      {
        // Convert parse tree for classdef object to
        // meta.class info (and stash it in the symbol
        // table?).  Return pointer to constructor?

        if (ov_fcn.is_defined ())
          panic_impossible ();

        bool is_at_folder = ! dispatch_type.empty ();

        std::shared_ptr<tree_classdef> cdef_obj
          = parser.classdef_object();

        return cdef_obj->make_meta_class (interp, is_at_folder);
      }
    else if (ov_fcn.is_defined ())
      {
        octave_function *fcn = ov_fcn.function_value ();

        fcn->maybe_relocate_end ();

        if (parser.m_parsing_subfunctions)
          {
            if (! parser.m_endfunction_found)
              parser.m_subfunction_names.reverse ();

            fcn->stash_subfunction_names (parser.m_subfunction_names);
          }

        return ov_fcn;
      }

    return octave_value ();
  }

  bool
  base_parser::finish_input (tree_statement_list *lst, bool at_eof)
  {
    m_lexer.m_end_of_input = at_eof;

    if (lst)
      {
        parse_tree_validator validator;

        lst->accept (validator);

        if (! validator.ok ())
          {
            delete lst;

            bison_error (validator.error_list ());

            return false;
          }
      }

    std::shared_ptr<tree_statement_list> tmp_lst (lst);

    statement_list (tmp_lst);

    return true;
  }

  // Check script or function for semantic errors.
  bool
  base_parser::validate_primary_fcn (void)
  {
    octave_user_code *code = m_primary_fcn.user_code_value ();

    if (code)
      {
        parse_tree_validator validator;

        code->accept (validator);

        if (! validator.ok ())
          {
            bison_error (validator.error_list ());

            return false;
          }
      }

    return true;
  }

  // Maybe print a warning if an assignment expression is used as the
  // test in a logical expression.

  void
  base_parser::maybe_warn_assign_as_truth_value (tree_expression *expr)
  {
    if (expr->is_assignment_expression ()
        && expr->paren_count () < 2)
      {
        if (m_lexer.m_fcn_file_full_name.empty ())
          warning_with_id
            ("Octave:assign-as-truth-value",
             "suggest parenthesis around assignment used as truth value");
        else
          warning_with_id
            ("Octave:assign-as-truth-value",
             "suggest parenthesis around assignment used as truth value near line %d, column %d in file '%s'",
             expr->line (), expr->column (), m_lexer.m_fcn_file_full_name.c_str ());
      }
  }

  // Maybe print a warning about switch labels that aren't constants.

  void
  base_parser::maybe_warn_variable_switch_label (tree_expression *expr)
  {
    if (! expr->is_constant ())
      {
        if (m_lexer.m_fcn_file_full_name.empty ())
          warning_with_id ("Octave:variable-switch-label",
                           "variable switch label");
        else
          warning_with_id
            ("Octave:variable-switch-label",
             "variable switch label near line %d, column %d in file '%s'",
             expr->line (), expr->column (), m_lexer.m_fcn_file_full_name.c_str ());
      }
  }

  void
  base_parser::maybe_warn_missing_semi (tree_statement_list *t)
  {
    if (m_curr_fcn_depth >= 0)
      {
        tree_statement *tmp = t->back ();

        if (tmp->is_expression ())
          warning_with_id
            ("Octave:missing-semicolon",
             "missing semicolon near line %d, column %d in file '%s'",
             tmp->line (), tmp->column (), m_lexer.m_fcn_file_full_name.c_str ());
      }
  }

  std::string
  get_help_from_file (const std::string& nm, bool& symbol_found,
                      std::string& full_file)
  {
    std::string retval;

    full_file = fcn_file_in_path (nm);

    std::string file = full_file;

    std::size_t file_len = file.length ();

    if ((file_len > 4 && file.substr (file_len-4) == ".oct")
        || (file_len > 4 && file.substr (file_len-4) == ".mex")
        || (file_len > 2 && file.substr (file_len-2) == ".m"))
      {
        file = sys::env::base_pathname (file);
        file = file.substr (0, file.find_last_of ('.'));

        std::size_t pos = file.find_last_of (sys::file_ops::dir_sep_str ());
        if (pos != std::string::npos)
          file = file.substr (pos+1);
      }

    if (! file.empty ())
      {
        interpreter& interp = __get_interpreter__ ("get_help_from_file");

        symbol_found = true;

        octave_value ov_fcn
          = parse_fcn_file (interp, full_file, file, "", "", "", true,
                            false, false, false);

        if (ov_fcn.is_defined ())
          {
            octave_function *fcn = ov_fcn.function_value ();

            if (fcn)
              retval = fcn->doc_string ();
          }
      }

    return retval;
  }

  std::string
  get_help_from_file (const std::string& nm, bool& symbol_found)
  {
    std::string file;
    return get_help_from_file (nm, symbol_found, file);
  }

  octave_value
  load_fcn_from_file (const std::string& file_name,
                      const std::string& dir_name,
                      const std::string& dispatch_type,
                      const std::string& package_name,
                      const std::string& fcn_name, bool autoload)
  {
    octave_value retval;

    unwind_protect frame;

    std::string nm = file_name;

    std::size_t nm_len = nm.length ();

    std::string file;

    bool relative_lookup = false;

    file = nm;

    if ((nm_len > 4 && nm.substr (nm_len-4) == ".oct")
        || (nm_len > 4 && nm.substr (nm_len-4) == ".mex")
        || (nm_len > 2 && nm.substr (nm_len-2) == ".m"))
      {
        nm = sys::env::base_pathname (file);
        nm = nm.substr (0, nm.find_last_of ('.'));

        std::size_t pos = nm.find_last_of (sys::file_ops::dir_sep_str ());
        if (pos != std::string::npos)
          nm = nm.substr (pos+1);
      }

    relative_lookup = ! sys::env::absolute_pathname (file);

    file = sys::env::make_absolute (file);

    int len = file.length ();

    interpreter& interp = __get_interpreter__ ("load_fcn_from_file");

    dynamic_loader& dyn_loader = interp.get_dynamic_loader ();

    if (len > 4 && file.substr (len-4, len-1) == ".oct")
      {
        if (autoload && ! fcn_name.empty ())
          nm = fcn_name;

        octave_function *tmpfcn
          = dyn_loader.load_oct (nm, file, relative_lookup);

        if (tmpfcn)
          {
            tmpfcn->stash_package_name (package_name);
            retval = octave_value (tmpfcn);
          }
      }
    else if (len > 4 && file.substr (len-4, len-1) == ".mex")
      {
        // Temporarily load m-file version of mex-file, if it exists,
        // to get the help-string to use.

        std::string doc_string;

        octave_value ov_fcn
          = parse_fcn_file (interp, file.substr (0, len - 2), nm, dir_name,
                            dispatch_type, package_name, false,
                            autoload, autoload, relative_lookup);

        if (ov_fcn.is_defined ())
          {
            octave_function *tmpfcn = ov_fcn.function_value ();

            if (tmpfcn)
              doc_string = tmpfcn->doc_string ();
          }

        octave_function *tmpfcn
          = dyn_loader.load_mex (nm, file, relative_lookup);

        if (tmpfcn)
          {
            tmpfcn->document (doc_string);
            tmpfcn->stash_package_name (package_name);

            retval = octave_value (tmpfcn);
          }
      }
    else if (len > 2)
      {
        retval = parse_fcn_file (interp, file, nm, dir_name,
                                 dispatch_type, package_name, true,
                                 autoload, autoload, relative_lookup);
      }

    return retval;
  }

DEFMETHOD (autoload, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{autoload_map} =} autoload ()
@deftypefnx {} {} autoload (@var{function}, @var{file})
@deftypefnx {} {} autoload (@dots{}, "remove")
Define @var{function} to autoload from @var{file}.

The second argument, @var{file}, should be an absolute filename or a file
name in the same directory as the function or script from which the autoload
command was run.  @var{file} @emph{should not} depend on the Octave load
path.

Normally, calls to @code{autoload} appear in PKG_ADD script files that are
evaluated when a directory is added to Octave's load path.  To avoid having
to hardcode directory names in @var{file}, if @var{file} is in the same
directory as the PKG_ADD script then

@example
autoload ("foo", "bar.oct");
@end example

@noindent
will load the function @code{foo} from the file @code{bar.oct}.  The above
usage when @code{bar.oct} is not in the same directory, or usages such as

@example
autoload ("foo", file_in_loadpath ("bar.oct"))
@end example

@noindent
are strongly discouraged, as their behavior may be unpredictable.

With no arguments, return a structure containing the current autoload map.

If a third argument @qcode{"remove"} is given, the function is cleared and
not loaded anymore during the current Octave session.

@seealso{PKG_ADD}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin == 1 || nargin > 3)
    print_usage ();

  tree_evaluator& tw = interp.get_evaluator ();

  if (nargin == 0)
    return octave_value (tw.get_autoload_map ());
  else
    {
      string_vector argv = args.make_argv ("autoload");

      if (nargin == 2)
        tw.add_autoload (argv[1], argv[2]);
      else if (nargin == 3)
        {
          if (argv[3] != "remove")
            error_with_id ("Octave:invalid-input-arg",
                           "autoload: third argument can only be 'remove'");

          tw.remove_autoload (argv[1], argv[2]);
        }
    }

  return octave_value_list ();
}

DEFMETHOD (mfilename, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} mfilename ()
@deftypefnx {} {} mfilename ("fullpath")
@deftypefnx {} {} mfilename ("fullpathext")
Return the name of the currently executing file.

The base name of the currently executing script or function is returned without
any extension.  If called from outside an m-file, such as the command line,
return the empty string.

Given the argument @qcode{"fullpath"}, include the directory part of the
filename, but not the extension.

Given the argument @qcode{"fullpathext"}, include the directory part of
the filename and the extension.
@seealso{inputname, dbstack}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin > 1)
    print_usage ();

  std::string opt;

  if (nargin == 1)
    opt = args(0).xstring_value ("mfilename: option argument must be a string");

  return octave_value (interp.mfilename (opt));
}

  // Execute the contents of a script file.  For compatibility with
  // Matlab, also execute a function file by calling the function it
  // defines with no arguments and nargout = 0.

  void
  source_file (const std::string& file_name, const std::string& context,
               bool verbose, bool require_file)
  {
    interpreter& interp = __get_interpreter__ ("source_file");

    interp.source_file (file_name, context, verbose, require_file);
  }

DEFMETHOD (source, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} source (@var{file})
@deftypefnx {} {} source (@var{file}, @var{context})
Parse and execute the contents of @var{file}.

Without specifying @var{context}, this is equivalent to executing commands
from a script file, but without requiring the file to be named
@file{@var{file}.m} or to be on the execution path.

Instead of the current context, the script may be executed in either the
context of the function that called the present function
(@qcode{"caller"}), or the top-level context (@qcode{"base"}).
@seealso{run}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  std::string file_name
    = args(0).xstring_value ("source: FILE must be a string");

  std::string context;
  if (nargin == 2)
    context = args(1).xstring_value ("source: CONTEXT must be a string");

  interp.source_file (file_name, context);

  return octave_value_list ();
}

  //! Evaluate an Octave function (built-in or interpreted) and return
  //! the list of result values.
  //!
  //! @param name The name of the function to call.
  //! @param args The arguments to the function.
  //! @param nargout The number of output arguments expected.
  //! @return A list of output values.  The length of the list is not
  //!         necessarily the same as @c nargout.

  octave_value_list
  feval (const char *name, const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ("feval");

    return interp.feval (name, args, nargout);
  }

  octave_value_list
  feval (const std::string& name, const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ("feval");

    return interp.feval (name, args, nargout);
  }

  octave_value_list
  feval (octave_function *fcn, const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ("feval");

    return interp.feval (fcn, args, nargout);
  }

  octave_value_list
  feval (const octave_value& val, const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ("feval");

    return interp.feval (val, args, nargout);
  }

  octave_value_list
  feval (const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ("feval");

    return interp.feval (args, nargout);
  }

DEFMETHOD (feval, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn {} {} feval (@var{name}, @dots{})
Evaluate the function named @var{name}.

Any arguments after the first are passed as inputs to the named function.
For example,

@example
@group
feval ("acos", -1)
     @result{} 3.1416
@end group
@end example

@noindent
calls the function @code{acos} with the argument @samp{-1}.

The function @code{feval} can also be used with function handles of any sort
(@pxref{Function Handles}).  Historically, @code{feval} was the only way to
call user-supplied functions in strings, but function handles are now
preferred due to the cleaner syntax they offer.  For example,

@example
@group
@var{f} = @@exp;
feval (@var{f}, 1)
    @result{} 2.7183
@var{f} (1)
    @result{} 2.7183
@end group
@end example

@noindent
are equivalent ways to call the function referred to by @var{f}.  If it
cannot be predicted beforehand whether @var{f} is a function handle,
function name in a string, or inline function then @code{feval} can be used
instead.
@end deftypefn */)
{
  if (args.length () == 0)
    print_usage ();

  return interp.feval (args, nargout);
}

DEFMETHOD (builtin, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn {} {[@dots{}] =} builtin (@var{f}, @dots{})
Call the base function @var{f} even if @var{f} is overloaded to another
function for the given type signature.

This is normally useful when doing object-oriented programming and there is
a requirement to call one of Octave's base functions rather than the
overloaded one of a new class.

A trivial example which redefines the @code{sin} function to be the
@code{cos} function shows how @code{builtin} works.

@example
@group
sin (0)
  @result{} 0
function y = sin (x), y = cos (x); endfunction
sin (0)
  @result{} 1
builtin ("sin", 0)
  @result{} 0
@end group
@end example
@end deftypefn */)
{
  octave_value_list retval;

  if (args.length () == 0)
    print_usage ();

  const std::string name (args(0).xstring_value ("builtin: function name (F) must be a string"));

  symbol_table& symtab = interp.get_symbol_table ();

  octave_value fcn = symtab.builtin_find (name);

  if (fcn.is_defined ())
    retval = interp.feval (fcn.function_value (), args.splice (0, 1), nargout);
  else
    error ("builtin: lookup for symbol '%s' failed", name.c_str ());

  return retval;
}

  void
  cleanup_statement_list (tree_statement_list **lst)
  {
    if (*lst)
      {
        delete *lst;
        *lst = nullptr;
      }
  }

DEFMETHOD (eval, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} eval (@var{try})
@deftypefnx {} {} eval (@var{try}, @var{catch})
Parse the string @var{try} and evaluate it as if it were an Octave
program.

If execution fails, evaluate the optional string @var{catch}.

The string @var{try} is evaluated in the current context, so any results
remain available after @code{eval} returns.

The following example creates the variable @var{A} with the approximate
value of 3.1416 in the current workspace.

@example
eval ("A = acos(-1);");
@end example

If an error occurs during the evaluation of @var{try} then the @var{catch}
string is evaluated, as the following example shows:

@example
@group
eval ('error ("This is a bad example");',
      'printf ("This error occurred:\n%s\n", lasterr ());');
     @print{} This error occurred:
        This is a bad example
@end group
@end example

Programming Note: if you are only using @code{eval} as an error-capturing
mechanism, rather than for the execution of arbitrary code strings,
Consider using try/catch blocks or unwind_protect/unwind_protect_cleanup
blocks instead.  These techniques have higher performance and don't
introduce the security considerations that the evaluation of arbitrary code
does.
@seealso{evalin, evalc, assignin, feval}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  if (! args(0).is_string () || args(0).rows () > 1 || args(0).ndims () != 2)
    error ("eval: TRY must be a string");

  std::string try_code = args(0).string_value ();

  if (nargin == 1)
    return interp.eval (try_code, nargout);
  else
    {
      if (! args(1).is_string () || args(1).rows () > 1
          || args(1).ndims () != 2)
        error ("eval: CATCH must be a string");

      std::string catch_code = args(1).string_value ();

      return interp.eval (try_code, catch_code, nargout);
    }
}

/*

%!shared x
%! x = 1;

%!assert (eval ("x"), 1)
%!assert (eval ("x;"))
%!assert (eval ("x;"), 1)

%!test
%! y = eval ("x");
%! assert (y, 1);

%!test
%! y = eval ("x;");
%! assert (y, 1);

%!test
%! eval ("x = 1;");
%! assert (x,1);

%!test
%! eval ("flipud = 2;");
%! assert (flipud, 2);

%!function y = __f ()
%!  eval ("flipud = 2;");
%!  y = flipud;
%!endfunction
%!assert (__f(), 2)

%!test <*35645>
%! [a,] = gcd (1,2);
%! [a,b,] = gcd (1, 2);

## Can't assign to a keyword
%!error eval ("switch = 13;")

%!shared str
%! str = "disp ('hello');";
%! str(:,:,2) = str(:,:,1);

%!error <TRY must be a string> eval (1)
%!error <TRY must be a string> eval (['a';'b'])
%!error <TRY must be a string> eval (str)

%!error <CATCH must be a string> eval (str(:,:,1), 1)
%!error <CATCH must be a string> eval (str(:,:,1), ['a';'b'])
%!error <CATCH must be a string> eval (str(:,:,1), str)

*/

DEFMETHOD (assignin, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn {} {} assignin (@var{context}, @var{varname}, @var{value})
Assign @var{value} to @var{varname} in context @var{context}, which
may be either @qcode{"base"} or @qcode{"caller"}.
@seealso{evalin}
@end deftypefn */)
{
  if (args.length () != 3)
    print_usage ();

  std::string context
    = args(0).xstring_value ("assignin: CONTEXT must be a string");

  std::string varname
    = args(1).xstring_value ("assignin: VARNAME must be a string");

  interp.assignin (context, varname, args(2));

  return octave_value_list ();
}

/*

%!error assignin ("base", "switch", "13")

*/

DEFMETHOD (evalin, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} evalin (@var{context}, @var{try})
@deftypefnx {} {} evalin (@var{context}, @var{try}, @var{catch})
Like @code{eval}, except that the expressions are evaluated in the context
@var{context}, which may be either @qcode{"caller"} or @qcode{"base"}.
@seealso{eval, assignin}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 2 || nargin > 3)
    print_usage ();

  std::string context
    = args(0).xstring_value ("evalin: CONTEXT must be a string");

  std::string try_code
    = args(1).xstring_value ("evalin: TRY must be a string");

  if (nargin == 3)
    {
      std::string catch_code
        = args(2).xstring_value ("evalin: CATCH must be a string");

      return interp.evalin (context, try_code, catch_code, nargout);
    }

  return interp.evalin (context, try_code, nargout);
}

DEFMETHOD (evalc, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{s} =} evalc (@var{try})
@deftypefnx {} {@var{s} =} evalc (@var{try}, @var{catch})
Parse and evaluate the string @var{try} as if it were an Octave program,
while capturing the output into the return variable @var{s}.

If execution fails, evaluate the optional string @var{catch}.

This function behaves like @code{eval}, but any output or warning messages
which would normally be written to the console are captured and returned in
the string @var{s}.

The @code{diary} is disabled during the execution of this function.  When
@code{system} is used, any output produced by external programs is
@emph{not} captured, unless their output is captured by the @code{system}
function itself.

@example
@group
s = evalc ("t = 42"), t
  @result{} s = t =  42

  @result{} t =  42
@end group
@end example
@seealso{eval, diary}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin == 0 || nargin > 2)
    print_usage ();

  // Flush pending output and redirect stdout/stderr to capturing
  // buffer.

  octave_stdout.flush ();
  std::cerr.flush ();

  std::stringbuf buffer;

  std::streambuf *old_out_buf = octave_stdout.rdbuf (&buffer);
  std::streambuf *old_err_buf = std::cerr.rdbuf (&buffer);

  // Restore previous output buffers no matter how control exits this
  // function.  There's no need to flush here.  That has already
  // happened for the normal execution path.  If an error happens during
  // the eval, then the message is stored in the exception object and we
  // will display it later, after the buffers have been restored.

  unwind_action act ([=] (void)
                             {
                               octave_stdout.rdbuf (old_out_buf);
                               std::cerr.rdbuf (old_err_buf);
                             });

  // Call standard eval function.

  int eval_nargout = std::max (0, nargout - 1);

  octave_value_list retval = Feval (interp, args, eval_nargout);

  // Make sure we capture all pending output.

  octave_stdout.flush ();
  std::cerr.flush ();

  retval.prepend (buffer.str ());

  return retval;
}

/*

%!test
%! [old_fmt, old_spacing] = format ();
%! unwind_protect
%!   format short;
%!   str = evalc ("1");
%!   assert (str, "ans = 1\n");
%! unwind_protect_cleanup
%!   format (old_fmt);
%!   format (old_spacing);
%! end_unwind_protect

%!assert (evalc ("1;"), "")

%!test
%! [s, y] = evalc ("1");
%! assert (s, "");
%! assert (y, 1);

%!test
%! [s, y] = evalc ("1;");
%! assert (s, "");
%! assert (y, 1);

%!test
%! [old_fmt, old_spacing] = format ();
%! unwind_protect
%!   format short;
%!   str = evalc ("y = 2");
%!   assert (str, "y = 2\n");
%!   assert (y, 2);
%! unwind_protect_cleanup
%!   format (old_fmt);
%!   format (old_spacing);
%! end_unwind_protect

%!test
%! assert (evalc ("y = 3;"), "");
%! assert (y, 3);

%!test
%! [s, a, b] = evalc ("deal (1, 2)");
%! assert (s, "");
%! assert (a, 1);
%! assert (b, 2);

%!function [a, b] = __f_evalc ()
%!  printf ("foo");
%!  fprintf (stdout, "bar ");
%!  disp (pi);
%!  a = 1;
%!  b = 2;
%!endfunction
%!test
%! [old_fmt, old_spacing] = format ();
%! unwind_protect
%!   format short;
%!   [s, a, b] = evalc ("__f_evalc ()");
%!   assert (s, "foobar 3.1416\n");
%!   assert (a, 1);
%!   assert (b, 2);
%! unwind_protect_cleanup
%!   format (old_fmt);
%!   format (old_spacing);
%! end_unwind_protect

%!error <foo> (evalc ("error ('foo')"))
%!error <bar> (evalc ("error ('foo')", "error ('bar')"))

%!test
%! warning ("off", "quiet", "local");
%! str = evalc ("warning ('foo')");
%! assert (str(1:13), "warning: foo\n");

%!test
%! warning ("off", "quiet", "local");
%! str = evalc ("error ('foo')", "warning ('bar')");
%! assert (str(1:13), "warning: bar\n");

%!error evalc ("switch = 13;")

*/

DEFUN (__parser_debug_flag__, args, nargout,
       doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} __parser_debug_flag__ ()
@deftypefnx {} {@var{old_val} =} __parser_debug_flag__ (@var{new_val})
Query or set the internal flag that determines whether Octave's parser
prints debug information as it processes an expression.
@seealso{__lexer_debug_flag__}
@end deftypefn */)
{
  octave_value retval;

  bool debug_flag = octave_debug;

  retval = set_internal_variable (debug_flag, args, nargout,
                                  "__parser_debug_flag__");

  octave_debug = debug_flag;

  return retval;
}

DEFMETHOD (__parse_file__, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn {} {} __parse_file__ (@var{file}, @var{verbose})
Undocumented internal function.
@end deftypefn */)
{
  octave_value retval;

  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  std::string file
    = args(0).xstring_value ("__parse_file__: expecting filename as argument");

  std::string full_file = sys::file_ops::tilde_expand (file);

  full_file = sys::env::make_absolute (full_file);

  std::string dir_name;

  std::size_t file_len = file.length ();

  if ((file_len > 4 && file.substr (file_len-4) == ".oct")
      || (file_len > 4 && file.substr (file_len-4) == ".mex")
      || (file_len > 2 && file.substr (file_len-2) == ".m"))
    {
      file = sys::env::base_pathname (file);
      file = file.substr (0, file.find_last_of ('.'));

      std::size_t pos = file.find_last_of (sys::file_ops::dir_sep_str ());
      if (pos != std::string::npos)
        {
          dir_name = file.substr (0, pos);
          file = file.substr (pos+1);
        }
    }

  if (nargin == 2)
    octave_stdout << "parsing " << full_file << std::endl;

  octave_value ov_fcn
    = parse_fcn_file (interp, full_file, file, dir_name, "", "", true,
                      false, false, false);

  return retval;
}

OCTAVE_NAMESPACE_END