////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1993-2024 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(...)

#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
%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 '~' '!'
%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     : '~'
                  {
                    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); }
                | '~' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('~', $2, $1); }
                | '!' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('!', $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); }
                | '~' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('~', $2, $1); }
                | '!' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('!', $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);

                    parser.maybe_convert_to_braindead_shortcircuit ($1);

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

                    parser.maybe_convert_to_braindead_shortcircuit ($4);

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

                    parser.maybe_convert_to_braindead_shortcircuit ($3);

                    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 stash_comment function_body function_end
                  {
                    OCTAVE_YYUSE ($5);

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

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

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::anonymous ());
                    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 stash_comment class_body stash_comment END
                  {
                    OCTAVE_YYUSE ($5);

                    lexer.m_parsing_classdef = false;

                    if (! ($$ = parser.make_classdef ($1, $3, $4, $6, $8, $10, $2, $7, $9)))
                      {
                        // make_classdef deleted $2, $3, $4, $6, $7, $8, $9
                        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);
                  }
                | '~' identifier
                  {
                    OCTAVE_YYUSE ($1);

                    $$ = parser.make_not_classdef_attribute ($2);
                  }
                | '!' 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_fcn++;
                    lexer.m_parsed_function_name.push (false);
                  }
                  method_decl1
                  {
                    lexer.m_defining_fcn--;
                    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_BEGIN_NAMESPACE(octave)

  class parse_exception : public std::runtime_error
  {
  public:

    parse_exception () = delete;

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

    OCTAVE_DEFAULT_COPY_MOVE_DELETE (parse_exception)

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

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

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

    int line () const { return m_line; }
    int column () 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 ()
      : m_scope (symbol_scope::anonymous ()), m_error_list ()
    { }

    OCTAVE_DISABLE_COPY_MOVE (parse_tree_validator)

    ~parse_tree_validator () = default;

    symbol_scope get_scope () const { return m_scope; }

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

    std::list<parse_exception> error_list () 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 () 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 ()
  {
    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 () const
  {
    return size () > 1 ? m_info[size()-2].first : symbol_scope::invalid ();
  }

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

  void base_parser::parent_scope_info::clear ()
  {
    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 (symbol_scope::invalid ()),
      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 ()
  {
    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 ()
  {
    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::invalid ();
    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 ()
  {
    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 ()
  {
    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 = m_lexer.m_interpreter;

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

  void
  base_parser::maybe_convert_to_braindead_shortcircuit (tree_expression*& expr)
  {
    if (expr->is_binary_expression ())
      {
        tree_binary_expression *binexp
          = dynamic_cast<tree_binary_expression *> (expr);

        tree_expression *lhs = binexp->lhs ();
        tree_expression *rhs = binexp->rhs ();

        maybe_convert_to_braindead_shortcircuit (lhs);
        maybe_convert_to_braindead_shortcircuit (rhs);

        // Operands may have changed.
        binexp->lhs (lhs);
        binexp->rhs (rhs);

        octave_value::binary_op op_type = binexp->op_type ();
        if (op_type == octave_value::op_el_and
            || op_type == octave_value::op_el_or)
          {
            binexp->preserve_operands ();

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

            delete expr;

            expr = new tree_braindead_shortcircuit_binary_expression
              (lhs, rhs, line, column, op_type);
          }
      }
  }

  // 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 '~':
      case '!':
        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");
          }
        else
          {
            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)
  {
    // Any comments at the beginning of a script file should be
    // attached to the first statement in the file or the END_SCRIPT
    // statement created by the parser.

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

    // First non-copyright comment in classdef body, before first
    // properties, methods, etc. block.

    tree_statement *first_stmt = cmds->front ();
    comment_list *leading_comments = first_stmt->comment_text ();

    std::string doc_string;
    if (leading_comments)
      doc_string = leading_comments->find_doc_string ();

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

    m_lexer.m_symtab_context.pop ();

    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, comment_list *bc)
  {
    // First non-copyright comments found above and below function keyword.
    comment_elt leading_doc_comment;
    comment_elt body_doc_comment;

    if (lc)
      leading_doc_comment = lc->find_doc_comment ();

    if (bc)
      body_doc_comment = bc->find_doc_comment ();

    // Choose which comment to use for doc string.

    // For ordinary functions, use the first comment that isn't empty.

    // If we are looking at a classdef method and there is a comment
    // prior to the function keyword and another after, then
    //
    //   * Choose the one outside the function definition if either of
    //     the comments use hash '#' characters.  This is the preferred
    //     Octave style.
    //
    //   * Choose the one inside the function definition if both
    //     comments use percent '%' characters.  This is
    //     Matlab-compatible behavior.

    // FIXME: maybe choose which comment to used by checking whether
    // any language extensions are noticed in the entire source file,
    // not just in the comments that are candidates to become the
    // function doc string.

    std::string doc_string;

    if (leading_doc_comment.empty ()
        || (m_lexer.m_parsing_classdef && ! body_doc_comment.empty ()
            && (! (leading_doc_comment.uses_hash_char () || body_doc_comment.uses_hash_char ()))))
      doc_string = body_doc_comment.text ();
    else
      doc_string = leading_doc_comment.text ();

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

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

    tree_function_def *retval = finish_function (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,
                               const std::string& doc_string)
  {
    // 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 doc string 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 doc_string objects in lexer.
    if (! doc_string.empty () && m_curr_fcn_depth == 0)
      fcn->document (doc_string);


    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 ()
  {
    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_fcn--;
    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 *bc, 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 bc;
        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 ();

            // First non-copyright comments found above and below
            // function keyword are candidates for the documentation
            // string.  Use the first one that is not empty.

            std::string doc_string;

            if (lc)
              doc_string = lc->find_doc_string ();

            if (doc_string.empty () && bc)
              doc_string = bc->find_doc_string ();

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

            // FIXME - pass body comment to tree_classdef constructor.

            retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (),
                                        doc_string, 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 bc;
            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::anonymous (), 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)
      {
        interpreter& interp = m_lexer.m_interpreter;

        symbol_table& symtab = interp.get_symbol_table ();

        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 ())
      {
        interpreter& interp = m_lexer.m_interpreter;

        tree_evaluator& tw = interp.get_evaluator ();

        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 = m_lexer.m_interpreter;

        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 ()
  {
    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 ()
  {
    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 ()
  {
    if (! m_reader)
      error ("push_parser::run requires valid input_reader");

    int exit_status = 0;

    std::string prompt
      = command_editor::decode_prompt_string (m_interpreter.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 (m_interpreter.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 ([=] () { ::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 ()
  {
    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__ ();

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

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

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

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

    return interp.feval (name, args, nargout);
  }

  octave_value_list
  feval (octave_function *fcn, const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ();

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

    return interp.feval (val, args, nargout);
  }

  octave_value_list
  feval (const octave_value_list& args, int nargout)
  {
    interpreter& interp = __get_interpreter__ ();

    return interp.feval (args, nargout);
  }

DEFMETHOD (feval, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn {} {@var{retval} =} 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;
}

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 ([=] ()
                             {
                               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_END_NAMESPACE(octave)
