/*

Copyright (C) 1993-2018 John W. Eaton
Copyright (C) 2009 David Grundberg
Copyright (C) 2009-2010 VZLU Prague
Copyright (C) 2016-2018 Oliver Heimlich

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 "builtin-defun-decls.h"
#include "call-stack.h"
#include "defaults.h"
#include "defun.h"
#include "dirfns.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 "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 "pt-funcall.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 *);

// List of autoloads (function -> file mapping).
static std::map<std::string, std::string> autoload_map;

// 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.m_lexer
#define scanner lexer.m_scanner

#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

%define api.prefix {octave_}

// 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
%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_funcall *tree_funcall_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_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> ADD_EQ SUB_EQ MUL_EQ DIV_EQ LEFTDIV_EQ POW_EQ
%token <tok_val> EMUL_EQ EDIV_EQ ELEFTDIV_EQ EPOW_EQ AND_EQ OR_EQ
%token <tok_val> EXPR_AND_AND EXPR_OR_OR
%token <tok_val> EXPR_AND EXPR_OR EXPR_NOT
%token <tok_val> EXPR_LT EXPR_LE EXPR_EQ EXPR_NE EXPR_GE EXPR_GT
%token <tok_val> LEFTDIV EMUL EDIV ELEFTDIV EPLUS EMINUS
%token <tok_val> HERMITIAN TRANSPOSE
%token <tok_val> PLUS_PLUS MINUS_MINUS POW EPOW
%token <tok_val> NUM IMAG_NUM
%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> 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> LEXICAL_ERROR

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

%token<dummy_type> '(' ')' '[' ']' '{' '}' '.' ',' ';' '@' '\n'

// Nonterminals we construct.
%type <dummy_type> indirect_ref_op decl_param_init
%type <dummy_type> push_fcn_symtab push_script_symtab begin_file
%type <dummy_type> param_list_beg param_list_end stmt_begin parse_error
%type <dummy_type> parsing_local_fcns
%type <comment_type> stash_comment
%type <tok_val> function_beg classdef_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 expr_no_assign
%type <tree_expression_type> simple_expr colon_expr assign_expr expression
%type <tree_identifier_type> identifier fcn_name magic_tilde
%type <tree_funcall_type> superclass_identifier 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 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> decl2 param_list_elt
%type <tree_decl_init_list_type> decl1
%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
%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 opt_attr_list
%type <tree_classdef_superclass_type> superclass
%type <tree_classdef_superclass_list_type> superclass_list opt_superclass_list
%type <tree_classdef_body_type> class_body
%type <tree_classdef_property_type> class_property
%type <tree_classdef_property_list_type> property_list
%type <tree_classdef_properties_block_type> properties_block
%type <tree_classdef_methods_list_type> methods_list
%type <tree_classdef_methods_block_type> methods_block
%type <tree_classdef_event_type> class_event
%type <tree_classdef_events_list_type> events_list
%type <tree_classdef_events_block_type> events_block
%type <tree_classdef_enum_type> class_enum
%type <tree_classdef_enum_list_type> enum_list
%type <tree_classdef_enum_block_type> enum_block
%type <tree_function_def_type> method_decl method
%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 '-' '+' EPLUS EMINUS
%left '*' '/' LEFTDIV EMUL EDIV ELEFTDIV
%right UNARY EXPR_NOT
%left POW EPOW HERMITIAN TRANSPOSE
%right PLUS_PLUS MINUS_MINUS
%left '(' '.' '{'

// How to clean up if there is a parse error.  We handle deleting tokens
// and comments seperately 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_funcall_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 $$; } <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'
                  {
                    $$ = nullptr;
                    parser.m_stmt_list = $1;
                    YYACCEPT;
                  }
                | simple_list END_OF_INPUT
                  {
                    $$ = nullptr;
                    lexer.m_end_of_input = true;
                    parser.m_stmt_list = $1;
                    YYACCEPT;
                  }
                | parse_error
                  {
                    $$ = nullptr;
                    YYABORT;
                  }
                ;

simple_list     : opt_sep_no_nl
                  {
                    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
                  { $$ = new octave::tree_statement_list (); }
                | 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
                  { $$ = new octave::tree_statement_list (); }
                | fcn_list
                  { $$ = $1; }
                ;

fcn_list        : fcn_list1 opt_sep
                  {
                    YYUSE ($2);

                    $$ = $1;
                  }
                ;

fcn_list1       : function
                  {
                    octave::tree_statement *stmt = parser.make_statement ($1);
                    $$ = new octave::tree_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;
                      }
                  }
                ;

word_list       : string
                  { $$ = new octave::tree_argument_list ($1); }
                | word_list string
                  {
                    $1->append ($2);
                    $$ = $1;
                  }
                ;

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

identifier      : NAME
                  {
                    octave::symbol_record sr = $1->sym_rec ();
                    $$ = new octave::tree_identifier (sr, $1->line (), $1->column ());
                  }
                ;

superclass_identifier
                : SUPERCLASSREF
                  {
                    std::string method_nm = $1->superclass_method_name ();
                    std::string class_nm = $1->superclass_class_name ();

                    $$ = parser.make_superclass_ref (method_nm, class_nm);
                  }
                ;

meta_identifier : METAQUERY
                  {
                    std::string class_nm = $1->text ();

                    $$ = parser.make_meta_class_query (class_nm);
                  }
                ;

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

constant        : NUM
                  { $$ = parser.make_constant (NUM, $1); }
                | IMAG_NUM
                  { $$ = parser.make_constant (IMAG_NUM, $1); }
                | string
                  { $$ = $1; }
                ;

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

matrix_rows     : cell_or_matrix_row
                  { $$ = $1 ? new octave::tree_matrix ($1) : nullptr; }
                | matrix_rows ';' cell_or_matrix_row
                  {
                    if ($1)
                      {
                        if ($3)
                          $1->append ($3);

                        $$ = $1;
                      }
                    else
                      $$ = $3 ? new octave::tree_matrix ($3) : nullptr;
                  }
                ;

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

cell_rows       : cell_or_matrix_row
                  { $$ = $1 ? new octave::tree_cell ($1) : nullptr; }
                | cell_rows ';' cell_or_matrix_row
                  {
                    if ($1)
                      {
                        if ($3)
                          $1->append ($3);

                        $$ = $1;
                      }
                    else
                      $$ = $3 ? new octave::tree_cell ($3) : nullptr;
                  }
                ;

// 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.

cell_or_matrix_row
                : // empty
                  { $$ = nullptr; }
                | ','
                  { $$ = nullptr; }
                | arg_list
                  { $$ = $1; }
                | arg_list ','
                  { $$ = $1; }
                | ',' arg_list
                  { $$ = $2; }
                | ',' arg_list ','
                  { $$ = $2; }
                ;

fcn_handle      : '@' FCN_HANDLE
                  {
                    $$ = parser.make_fcn_handle ($2);
                    lexer.m_looking_at_function_handle--;
                  }
                ;

anon_fcn_handle : '@' param_list stmt_begin expr_no_assign
                  {
                    $$ = parser.make_anon_fcn_handle ($2, $4);
                    lexer.m_nesting_level.remove ();
                  }
                | '@' param_list stmt_begin error
                  {
                    YYUSE ($2);

                    $$ = 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 ')'
                  { $$ = $2->mark_in_parens (); }
                ;

magic_colon     : ':'
                  {
                    YYUSE ($1);

                    octave_value tmp (octave_value::magic_colon_t);
                    $$ = new octave::tree_constant (tmp);
                  }
                ;

magic_tilde     : EXPR_NOT
                  {
                    YYUSE ($1);

                    $$ = new octave::tree_black_hole ();
                  }
                ;

arg_list        : expression
                  { $$ = new octave::tree_argument_list ($1); }
                | magic_colon
                  { $$ = new octave::tree_argument_list ($1); }
                | magic_tilde
                  { $$ = new octave::tree_argument_list ($1); }
                | arg_list ',' magic_colon
                  {
                    $1->append ($3);
                    $$ = $1;
                  }
                | arg_list ',' magic_tilde
                  {
                    $1->append ($3);
                    $$ = $1;
                  }
                | arg_list ',' expression
                  {
                    $1->append ($3);
                    $$ = $1;
                  }
                ;

indirect_ref_op : '.'
                  {
                    $$ = 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 '(' ')'
                  {
                    $$ = parser.make_index_expression ($1, nullptr, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | oper_expr '(' arg_list ')'
                  {
                    $$ = parser.make_index_expression ($1, $3, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | oper_expr '{' '}'
                  {
                    $$ = parser.make_index_expression ($1, nullptr, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | oper_expr '{' arg_list '}'
                  {
                    $$ = 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 ')'
                  { $$ = parser.make_indirect_ref ($1, $4); }
                | PLUS_PLUS oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); }
                | MINUS_MINUS oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); }
                | EXPR_NOT oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); }
                | '+' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('+', $2, $1); }
                | '-' oper_expr %prec UNARY
                  { $$ = parser.make_prefix_op ('-', $2, $1); }
                | oper_expr POW power_expr
                  { $$ = parser.make_binary_op (POW, $1, $2, $3); }
                | oper_expr EPOW power_expr
                  { $$ = parser.make_binary_op (EPOW, $1, $2, $3); }
                | oper_expr '+' oper_expr
                  { $$ = parser.make_binary_op ('+', $1, $2, $3); }
                | oper_expr '-' oper_expr
                  { $$ = parser.make_binary_op ('-', $1, $2, $3); }
                | oper_expr '*' oper_expr
                  { $$ = parser.make_binary_op ('*', $1, $2, $3); }
                | oper_expr '/' oper_expr
                  { $$ = parser.make_binary_op ('/', $1, $2, $3); }
                | oper_expr EPLUS oper_expr
                  { $$ = parser.make_binary_op ('+', $1, $2, $3); }
                | oper_expr EMINUS 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 '(' ')'
                  {
                    $$ = parser.make_index_expression ($1, nullptr, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | power_expr '(' arg_list ')'
                  {
                    $$ = parser.make_index_expression ($1, $3, '(');
                    if (! $$)
                      {
                        // make_index_expression deleted $1 and $3.
                        YYABORT;
                      }
                  }
                | power_expr '{' '}'
                  {
                    $$ = parser.make_index_expression ($1, nullptr, '{');
                    if (! $$)
                      {
                        // make_index_expression deleted $1.
                        YYABORT;
                      }
                  }
                | power_expr '{' arg_list '}'
                  {
                    $$ = 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 ')'
                  { $$ = parser.make_indirect_ref ($1, $4); }
                | PLUS_PLUS power_expr %prec POW
                  { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); }
                | MINUS_MINUS power_expr %prec POW
                  { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); }
                | EXPR_NOT power_expr %prec POW
                  { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); }
                | '+' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('+', $2, $1); }
                | '-' power_expr %prec POW
                  { $$ = parser.make_prefix_op ('-', $2, $1); }
                ;

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

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

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

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

                    if (! $$)
                      {
                        // finish_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); }
                ;

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

expression      : expr_no_assign
                  { $$ = $1; }
                | assign_expr
                  {
                    if (! $1)
                      YYABORT;

                    $$ = $1;
                  }

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

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

// =====================
// Declaration statemnts
// =====================

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

decl1           : decl2
                  { $$ = new octave::tree_decl_init_list ($1); }
                | decl1 decl2
                  {
                    $1->append ($2);
                    $$ = $1;
                  }
                ;

decl_param_init : // empty
                  {
                    $$ = 0;
                    lexer.m_looking_at_initializer_expression = true;
                  }

decl2           : identifier
                  { $$ = new octave::tree_decl_elt ($1); }
                | identifier '=' decl_param_init expression
                  {
                    YYUSE ($2);

                    lexer.m_looking_at_initializer_expression = false;
                    $$ = new octave::tree_decl_elt ($1, $4);
                  }
                ;

// ====================
// 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
                  {
                    $1->append ($2);
                    $$ = $1;
                  }
                ;

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

                    $1->mark_braindead_shortcircuit ();

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

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

                    $4->mark_braindead_shortcircuit ();

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

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

                    $$ = new octave::tree_if_clause ($4, $2);
                  }
                ;

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

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

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

case_list       : // empty
                  { $$ = new octave::tree_switch_case_list (); }
                | default_case
                  { $$ = new octave::tree_switch_case_list ($1); }
                | case_list1
                  { $$ = $1; }
                | case_list1 default_case
                  {
                    $1->append ($2);
                    $$ = $1;
                  }
                ;

case_list1      : switch_case
                  { $$ = new octave::tree_switch_case_list ($1); }
                | case_list1 switch_case
                  {
                    $1->append ($2);
                    $$ = $1;
                  }
                ;

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

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

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

                    $$ = new octave::tree_switch_case ($4, $2);
                  }
                ;

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

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

                    $3->mark_braindead_shortcircuit ();

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

                    $$ = parser.make_do_until_command ($5, $4, $6, $2);
                  }
                | FOR stash_comment assign_lhs '=' expression stmt_begin opt_sep opt_list END
                  {
                    YYUSE ($4);
                    YYUSE ($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
                  {
                    YYUSE ($5);
                    YYUSE ($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
                  {
                    YYUSE ($4);
                    YYUSE ($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
                  {
                    YYUSE ($5);
                    YYUSE ($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
                  { $$ = parser.make_continue_command ($1); }
                | FUNC_RET
                  { $$ = parser.make_return_command ($1); }
                ;

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

except_command  : UNWIND stash_comment opt_sep opt_list CLEANUP
                  stash_comment opt_sep opt_list END
                  {
                    YYUSE ($3);
                    YYUSE ($5);
                    YYUSE ($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
                  {
                    YYUSE ($3);
                    YYUSE ($5);
                    YYUSE ($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
                  {
                    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
                  {
                    $$ = 0;

                    parser.m_curr_fcn_depth++;

                    if (parser.m_max_fcn_depth < parser.m_curr_fcn_depth)
                      parser.m_max_fcn_depth = parser.m_curr_fcn_depth;

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

                    if (! lexer.m_reading_script_file
                        && parser.m_curr_fcn_depth == 1
                        && ! parser.m_parsing_subfunctions)
                      parser.m_primary_fcn_scope
                        = lexer.m_symtab_context.curr_scope ();

                    if (lexer.m_reading_script_file
                        && parser.m_curr_fcn_depth > 1)
                      {
                        parser.bison_error ("nested functions not implemented in this context");
                        YYABORT;
                      }
                  }
                ;

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

param_list_beg  : '('
                  {
                    $$ = 0;
                    lexer.m_looking_at_parameter_list = true;

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

param_list_end  : ')'
                  {
                    $$ = 0;
                    lexer.m_looking_at_parameter_list = false;
                    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
                  { $$ = nullptr; }
                | 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
                  { $$ = new octave::tree_parameter_list ($1); }
                | param_list2 ',' param_list_elt
                  {
                    $1->append ($3);
                    $$ = $1;
                  }
                ;

param_list_elt  : decl2
                  { $$ = $1; }
                | magic_tilde
                  { $$ = new octave::tree_decl_elt ($1); }
                ;

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

return_list     : '[' ']'
                  {
                    lexer.m_looking_at_return_list = false;

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

                    octave::tree_parameter_list *tmp = new octave::tree_parameter_list ($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 ']'
                  {
                    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
                  { $$ = new octave::tree_parameter_list (new octave::tree_decl_elt ($1)); }
                | return_list1 ',' identifier
                  {
                    $1->append (new octave::tree_decl_elt ($3));
                    $$ = $1;
                  }
                ;

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

parsing_local_fcns
                : // empty
                  { parser.m_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
                  {
                    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_ptr.

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

                        delete $3;
                      }
                    else
                      {
                        octave::tree_statement *end_of_script
                          = parser.make_end ("endscript", true,
                                             lexer.m_input_line_number,
                                             lexer.m_current_input_column);

                        parser.make_script ($3, end_of_script);
                      }

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

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

                    parser.finish_classdef_file ($3, $6);

                    $$ = 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
                  {
                    std::string id = $1->name ();

                    // Make classdef local functions unique from
                    // classdef methods.

                    if (parser.m_parsing_local_functions
                        && parser.m_curr_fcn_depth == 1)
                      id = lexer.m_fcn_file_name + ">" + id;

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

                        delete $1;

                        YYABORT;
                      }

                    octave::symbol_scope curr_scope
                      = lexer.m_symtab_context.curr_scope ();
                    curr_scope.cache_name (id);

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

                    $$ = $1;
                  }
                | GET '.' identifier
                  {
                    YYUSE ($1);

                    lexer.m_parsed_function_name.top () = true;
                    lexer.m_maybe_classdef_get_set_method = false;
                    lexer.m_parsing_classdef_get_method = true;
                    $$ = $3;
                  }
                | SET '.' identifier
                  {
                    YYUSE ($1);

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

function_end    : END
                  {
                    parser.m_endfunction_found = true;

                    if (parser.end_token_ok ($1, octave::token::function_end))
                      $$ = parser.make_end ("endfunction", false,
                                            $1->line (), $1->column ());
                    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.m_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,
                                          lexer.m_input_line_number,
                                          lexer.m_current_input_column);
                  }
                ;

function        : function_beg stash_comment fcn_name
                  opt_param_list opt_sep opt_list function_end
                  {
                    YYUSE ($5);

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

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

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

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

                    // Create invalid parent scope.
                    lexer.m_symtab_context.push (octave::symbol_scope ());
                    lexer.m_parsing_classdef = true;
                    $$ = $1;
                  }
                ;

classdef        : classdef_beg stash_comment opt_attr_list identifier opt_superclass_list opt_sep class_body opt_sep END
                  {
                    YYUSE ($6);
                    YYUSE ($8);

                    lexer.m_parsing_classdef = false;

                    if (! ($$ = parser.make_classdef ($1, $3, $4, $5, $7, $9, $2)))
                      {
                        // make_classdef deleted $3, $4, $5, and $7.
                        YYABORT;
                      }
                  }
                | classdef_beg stash_comment opt_attr_list identifier opt_superclass_list opt_sep END
                  {
                    YYUSE ($6);

                    lexer.m_parsing_classdef = false;

                    if (! ($$ = parser.make_classdef ($1, $3, $4, $5, nullptr,
                                                      $7, $2)))
                      {
                        // make_classdef deleted $3, $4, and $5.
                        YYABORT;
                      }
                  }
                ;

opt_attr_list   : // empty
                  { $$ = nullptr; }
                | '(' attr_list ')'
                  { $$ = $2; }
                ;

attr_list       : attr
                  { $$ = new octave::tree_classdef_attribute_list ($1); }
                | attr_list ',' attr
                  {
                    $1->append ($3);
                    $$ = $1;
                  }
                ;

attr            : identifier
                  { $$ = new octave::tree_classdef_attribute ($1); }
                | identifier '=' decl_param_init expression
                  {
                    YYUSE ($2);

                    lexer.m_looking_at_initializer_expression = false;
                    $$ = new octave::tree_classdef_attribute ($1, $4);
                  }
                | EXPR_NOT identifier
                  {
                    YYUSE ($1);

                    $$ = new octave::tree_classdef_attribute ($2, false);
                  }
                ;

opt_superclass_list
                : // empty
                  { $$ = nullptr; }
                | superclass_list
                  { $$ = $1; }
                ;

superclass_list : EXPR_LT
                  {
                    YYUSE ($1);

                    lexer.enable_fq_identifier ();
                  }
                  superclass
                  { $$ = new octave::tree_classdef_superclass_list ($3); }
                | superclass_list EXPR_AND
                  {
                    YYUSE ($2);

                    lexer.enable_fq_identifier ();
                  }
                  superclass
                  {
                    $1->append ($4);
                    $$ = $1;
                  }
                ;

superclass      : FQ_IDENT
                  { $$ = new octave::tree_classdef_superclass ($1->text ()); }
                ;

class_body      : properties_block
                  { $$ = new octave::tree_classdef_body ($1); }
                | methods_block
                  { $$ = new octave::tree_classdef_body ($1); }
                | events_block
                  { $$ = new octave::tree_classdef_body ($1); }
                | enum_block
                  { $$ = new octave::tree_classdef_body ($1); }
                | class_body opt_sep properties_block
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                | class_body opt_sep methods_block
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                | class_body opt_sep events_block
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                | class_body opt_sep enum_block
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                ;

properties_block
                : PROPERTIES stash_comment opt_attr_list opt_sep property_list opt_sep END
                  {
                    YYUSE ($4);
                    YYUSE ($6);

                    if (! ($$ = parser.make_classdef_properties_block
                           ($1, $3, $5, $7, $2)))
                      {
                        // make_classdef_properties_block delete $3 and $5.
                        YYABORT;
                      }
                  }
                | PROPERTIES stash_comment opt_attr_list opt_sep END
                  {
                    YYUSE ($4);

                    if (! ($$ = parser.make_classdef_properties_block
                           ($1, $3, nullptr, $5, $2)))
                      {
                        // make_classdef_properties_block delete $3.
                        YYABORT;
                      }
                  }
                ;

property_list
                : class_property
                  { $$ = new octave::tree_classdef_property_list ($1); }
                | property_list sep class_property
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                ;

class_property  : identifier
                  { $$ = new octave::tree_classdef_property ($1); }
                | identifier '=' decl_param_init expression
                  {
                    YYUSE ($2);

                    lexer.m_looking_at_initializer_expression = false;
                    $$ = new octave::tree_classdef_property ($1, $4);
                  }
                ;

methods_block   : METHODS stash_comment opt_attr_list opt_sep methods_list opt_sep END
                  {
                    YYUSE ($4);
                    YYUSE ($6);

                    if (! ($$ = parser.make_classdef_methods_block
                           ($1, $3, $5, $7, $2)))
                      {
                        // make_classdef_methods_block deleted $3 and $5.
                        YYABORT;
                      }
                  }
                | METHODS stash_comment opt_attr_list opt_sep END
                  {
                    YYUSE ($4);

                    if (! ($$ = parser.make_classdef_methods_block
                           ($1, $3, nullptr, $5, $2)))
                      {
                        // make_classdef_methods_block deleted $3.
                        YYABORT;
                      }
                  }
                ;
                ;

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 '='
                  {
                    YYUSE ($3);

                    lexer.m_defining_func++;
                    lexer.m_parsed_function_name.push (false);
                  }
                  method_decl1
                  {
                    lexer.m_defining_func--;
                    lexer.m_parsed_function_name.pop ();
                    $$ = parser.finish_classdef_external_method ($5, $2, $1);
                  }
                ;

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

methods_list    : method
                  {
                    octave_value fcn;
                    if ($1)
                      fcn = $1->function ();
                    delete $1;
                    $$ = new octave::tree_classdef_methods_list (fcn);
                  }
                | methods_list opt_sep method
                  {
                    YYUSE ($2);

                    octave_value fcn;
                    if ($3)
                      fcn = $3->function ();
                    delete $3;

                    $1->append (fcn);
                    $$ = $1;
                  }
                ;

events_block    : EVENTS stash_comment opt_attr_list opt_sep events_list opt_sep END
                  {
                    YYUSE ($4);
                    YYUSE ($6);

                    if (! ($$ = parser.make_classdef_events_block
                           ($1, $3, $5, $7, $2)))
                      {
                        // make_classdef_events_block deleted $3 and $5.
                        YYABORT;
                      }
                  }
                | EVENTS stash_comment opt_attr_list opt_sep END
                  {
                    YYUSE ($4);

                    if (! ($$ = parser.make_classdef_events_block
                           ($1, $3, nullptr, $5, $2)))
                      {
                        // make_classdef_events_block deleted $3.
                        YYABORT;
                      }
                  }
                ;

events_list     : class_event
                  { $$ = new octave::tree_classdef_events_list ($1); }
                | events_list opt_sep class_event
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                ;

class_event     : identifier
                  { $$ = new octave::tree_classdef_event ($1); }
                ;

enum_block      : ENUMERATION stash_comment opt_attr_list opt_sep enum_list opt_sep END
                  {
                    YYUSE ($4);
                    YYUSE ($6);

                    if (! ($$ = parser.make_classdef_enum_block
                           ($1, $3, $5, $7, $2)))
                      {
                        // make_classdef_enum_block deleted $3 and $5.
                        YYABORT;
                      }
                  }
                | ENUMERATION stash_comment opt_attr_list opt_sep END
                  {
                    YYUSE ($4);

                    if (! ($$ = parser.make_classdef_enum_block
                           ($1, $3, nullptr, $5, $2)))
                      {
                        // make_classdef_enum_block deleted $3.
                        YYABORT;
                      }
                  }
                ;

enum_list       : class_enum
                  { $$ = new octave::tree_classdef_enum_list ($1); }
                | enum_list opt_sep class_enum
                  {
                    YYUSE ($2);

                    $1->append ($3);
                    $$ = $1;
                  }
                ;

class_enum      : identifier '(' expression ')'
                  { $$ = new octave::tree_classdef_enum ($1, $3); }
                ;

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

stmt_begin      : // empty
                  {
                    $$ = 0;
                    lexer.m_at_beginning_of_statement = 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       : ','
                  { $$ = ','; }
                | ';'
                  { $$ = ';'; }
                | sep_no_nl ','
                  { $$ = $1; }
                | sep_no_nl ';'
                  { $$ = $1; }
                ;

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

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

nl              : '\n'
                  { $$ = '\n'; }
                | nl '\n'
                  { $$ = $1; }
                ;

sep             : ','
                  { $$ = ','; }
                | ';'
                  { $$ = ';'; }
                | '\n'
                  { $$ = '\n'; }
                | sep ','
                  { $$ = $1; }
                | sep ';'
                  { $$ = $1; }
                | sep '\n'
                  { $$ = $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);
}

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

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

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

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

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

    std::string full_name;

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

    m_all_names.insert (full_name);

    return true;
  }

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

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

    return true;
  }

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

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

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

  base_parser::base_parser (base_lexer& lxr)
    : m_endfunction_found (false), m_autoloading (false),
      m_fcn_file_from_relative_lookup (false),
      m_parsing_subfunctions (false), m_parsing_local_functions (false),
      m_max_fcn_depth (0), m_curr_fcn_depth (0), m_primary_fcn_scope (),
      m_curr_class_name (), m_curr_package_name (), m_function_scopes (),
      m_primary_fcn_ptr (nullptr), m_subfunction_names (),
      m_classdef_object (nullptr), m_stmt_list (nullptr), m_lexer (lxr),
      m_parser_state (yypstate_new ())
  { }

  base_parser::~base_parser (void)
  {
    delete m_stmt_list;

    delete &m_lexer;

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

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

  void
  base_parser::reset (void)
  {
    m_endfunction_found = false;
    m_autoloading = false;
    m_fcn_file_from_relative_lookup = false;
    m_parsing_subfunctions = false;
    m_parsing_local_functions = false;
    m_max_fcn_depth = 0;
    m_curr_fcn_depth = 0;
    m_primary_fcn_scope = symbol_scope ();
    m_curr_class_name = "";
    m_curr_package_name = "";
    m_function_scopes.clear ();
    m_primary_fcn_ptr  = nullptr;
    m_subfunction_names.clear ();
    m_classdef_object = nullptr;

    delete m_stmt_list;
    m_stmt_list = nullptr;

    m_lexer.reset ();

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

// Error mesages for mismatched end tokens.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    default:
      panic_impossible ();
      break;
    }

  return retval;
}

namespace octave
{
  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->line (), tok->column ());
  }

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

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

  // Make a constant.

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

    tree_constant *retval = nullptr;

    switch (op)
      {
      case NUM:
        {
          octave_value tmp (tok_val->number ());
          retval = new tree_constant (tmp, l, c);
          retval->stash_original_text (tok_val->text_rep ());
        }
        break;

      case IMAG_NUM:
        {
          octave_value tmp (Complex (0.0, tok_val->number ()));
          retval = new tree_constant (tmp, 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;
  }

  // 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)
  {
    // FIXME: need to get these from the location of the @ symbol.
    int l = m_lexer.m_input_line_number;
    int c = m_lexer.m_current_input_column;

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

    tree_anon_fcn_handle *retval
      = new tree_anon_fcn_handle (param_list, expr, fcn_scope,
                                  parent_scope, l, c);

    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: " << l << " column: " << c;

    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;

    unwind_protect frame;

    frame.protect_var (discard_error_messages);
    frame.protect_var (discard_warning_messages);

    discard_error_messages = true;
    discard_warning_messages = true;

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

        return retval;
      }

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

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

    if (base->is_constant () && limit->is_constant ()
        && (! incr || (incr && incr->is_constant ())))
      {
        try
          {
            tree_evaluator& tw
              = __get_evaluator__ ("finish_colon_expression");

            octave_value tmp = tw.evaluate (e);

            tree_constant *tc_retval
              = new tree_constant (tmp, e->line (), e->column ());

            std::ostringstream buf;

            tree_print_code tpc (buf);

            e->accept (tpc);

            tc_retval->stash_original_text (buf.str ());

            delete e;

            retval = tc_retval;
          }
        catch (const execution_exception&)
          {
            interpreter::recover_from_exception ();
          }
      }
    else
      retval = e;

    return retval;
  }

  // Build a binary expression.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      default:
        panic_impossible ();
        break;
      }

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

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

  // Build a boolean expression.

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

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

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

      default:
        panic_impossible ();
        break;
      }

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

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

  // Build a prefix expression.

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

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

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

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

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

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

      default:
        panic_impossible ();
        break;
      }

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

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

  // Build a postfix expression.

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

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

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

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

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

      default:
        panic_impossible ();
        break;
      }

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

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

  // Build an unwind-protect command.

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

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

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

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

        end_token_error (end_tok, token::unwind_protect_end);
      }

    return retval;
  }

  // Build a try-catch command.

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

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

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

        tree_identifier *id = nullptr;

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

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

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

                    cleanup_stmts->pop_front ();

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

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

        end_token_error (end_tok, token::try_catch_end);
      }

    return retval;
  }

  // Build a while command.

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

    maybe_warn_assign_as_truth_value (expr);

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

        m_lexer.m_looping--;

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

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

        end_token_error (end_tok, token::while_end);
      }

    return retval;
  }

  // Build a do-until command.

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

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

    m_lexer.m_looping--;

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

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

  // Build a for command.

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

    bool parfor = tok_id == PARFOR;

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

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

        m_lexer.m_looping--;

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

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

            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
              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 in a loop in the same file as loop command");
        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 ();

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

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

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

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

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

  // 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", l, c);

        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 ())
            && is_keyword (tmp->name ()))
          {
            std::string kw = tmp->name ();

            delete tmp;
            delete lhs;
            delete rhs;

            bison_error ("invalid assignment to keyword \"" + kw + "\"", l, c);

            return nullptr;
          }

        delete lhs;

        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 (is_keyword (kw))
              {
                delete lhs;
                delete rhs;

                bison_error ("invalid assignment to keyword \"" + kw + "\"",
                             l, c);

                return nullptr;
              }
          }

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

  // Define a script.

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

    cmds->append (end_script);

    symbol_scope script_scope = m_lexer.m_symtab_context.curr_scope ();

    script_scope.cache_name (m_lexer.m_fcn_file_full_name);

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

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

    sys::time now;

    script->stash_fcn_file_time (now);

    m_primary_fcn_ptr = script;
  }

  // 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)
  {
    tree_function_def *retval = nullptr;

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

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

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

    recover_from_parsing_function ();

    return retval;
  }

  // Begin defining a function.

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

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

    delete id;

    if (m_lexer.m_parsing_classdef_get_method)
      id_name.insert (0, "get.");
    else if (m_lexer.m_parsing_classdef_set_method)
      id_name.insert (0, "set.");

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

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

    body->append (end_fcn_stmt);

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

    if (fcn)
      {
        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 == 1 && ! 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;

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

    if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_classdef_file || m_autoloading)
      {
        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 ();

        if (m_fcn_file_from_relative_lookup)
          fcn->mark_relative ();

        if (m_lexer.m_parsing_class_method)
          {
            if (m_curr_class_name == id_name)
              fcn->mark_as_class_constructor ();
            else
              fcn->mark_as_class_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 (! 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 ());
      }

    fcn->stash_function_name (id_name);

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

        m_lexer.m_help_text = "";
      }

    if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 1
        && ! m_parsing_subfunctions)
      m_primary_fcn_ptr = fcn;

    return fcn;
  }

  tree_statement *
  base_parser::make_end (const std::string& type, bool eof, int l, int c)
  {
    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->mark_as_formal_parameters ();

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

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

        symbol_scope fcn_scope = fcn->scope ();
        fcn_scope.cache_name (tmp);

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

        fcn->define_ret_list (ret_list);

        if (m_curr_fcn_depth > 1 || m_parsing_subfunctions)
          {
            fcn->stash_fcn_location (l, c);
            fcn->stash_parent_fcn_name (m_lexer.m_fcn_file_name);

            octave_value ov_fcn (fcn);

            if (m_endfunction_found && m_function_scopes.size () > 1)
              {
                fcn->mark_as_nested_function ();
                fcn_scope.mark_nested ();

                symbol_scope pscope = m_function_scopes.parent_scope ();
                fcn_scope.set_parent (pscope);
                pscope.install_nestfunction (nm, ov_fcn, fcn_scope);
              }
            else
              {
                fcn->mark_as_subfunction ();
                m_subfunction_names.push_back (nm);
                fcn_scope.set_parent (m_primary_fcn_scope);
                m_primary_fcn_scope.install_subfunction (nm, ov_fcn);
              }
          }

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

        if (! m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 1)
          {
            // 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_ptr (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);
          }
      }

    return retval;
  }

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

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

    m_curr_fcn_depth--;
    m_function_scopes.pop ();

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

  tree_funcall *
  base_parser::make_superclass_ref (const std::string& method_nm,
                                    const std::string& class_nm)
  {
    octave_value_list args;

    args(1) = class_nm;
    args(0) = method_nm;

    symbol_table& symtab
      = __get_symbol_table__ ("base_parser::make_superclass_ref");

    octave_value fcn
      = symtab.find_built_in_function ("__superclass_reference__");

    return new tree_funcall (fcn, args);
  }

  tree_funcall *
  base_parser::make_meta_class_query (const std::string& class_nm)
  {
    octave_value_list args;

    args(0) = class_nm;

    symbol_table& symtab
      = __get_symbol_table__ ("base_parser::make_meta_class_query");

    octave_value fcn
      = symtab.find_built_in_function ("__meta_class_query__");

    return new tree_funcall (fcn, args);
  }

  // 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.

  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)
  {
    tree_classdef *retval = nullptr;

    m_lexer.m_symtab_context.pop ();

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

    std::string nm = m_lexer.m_fcn_file_name;

    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 != cls_name)
      {
        delete a;
        delete id;
        delete sc;
        delete body;

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

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

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

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

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

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

    return retval;
  }

  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)
  {
    tree_classdef_properties_block *retval = nullptr;

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

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

        if (! plist)
          plist = new tree_classdef_property_list ();

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

        end_token_error (end_tok, token::properties_end);
      }

    return retval;
  }

  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)
  {
    tree_classdef_methods_block *retval = nullptr;

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

        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;

        end_token_error (end_tok, token::methods_end);
      }

    return retval;
  }

  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)
  {
    tree_classdef_events_block *retval = nullptr;

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

        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;

        end_token_error (end_tok, token::events_end);
      }

    return retval;
  }

  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)
  {
    tree_classdef_enum_block *retval = nullptr;

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

        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;

        end_token_error (end_tok, token::enumeration_end);
      }

    return retval;
  }

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

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

    if (! m_curr_class_name.empty ())
      {

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

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

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

            retval = new octave_user_function (symbol_scope (), pl);

            retval->stash_function_name (mname);

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

            retval->stash_fcn_location (l, c);
          }
        else
          bison_error ("invalid external method declaration, an external "
                       "method cannot be the class constructor, `delete' "
                       "or have a dot (.) character in its name");
      }
    else
      bison_error ("external methods are only allowed in @-folders");

    if (! retval)
      delete id;

    return retval;
  }

  tree_function_def *
  base_parser::finish_classdef_external_method (octave_user_function *fcn,
                                                tree_parameter_list *ret_list,
                                                comment_list *cl)
  {
    if (ret_list)
      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);
  }

  void
  base_parser::finish_classdef_file (tree_classdef *cls,
                                     tree_statement_list *local_fcns)
  {
    if (m_lexer.m_reading_classdef_file)
      m_classdef_object = cls;

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

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

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

            octave_value ov_fcn = fcn_def->function ();
            octave_function *fcn = ov_fcn.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;
      }
  }

  // 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 =
              static_cast<tree_index_expression *> (expr);

            tmp->append (args, type);

            retval = tmp;
          }
        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 = static_cast<tree_index_expression *> (expr);

        tmp->append (elt);

        retval = tmp;
      }
    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 = static_cast<tree_index_expression *> (expr);

        tmp->append (elt);

        retval = tmp;
      }
    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;
  }

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

    size_t len = lst->length ();

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

        tree_identifier *id = elt->ident ();

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

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

    return true;
  }

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

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

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

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

            break;
          }
      }

    return retval;
  }

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

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

        octave_value ov = tw.evaluate (e);

        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)
  {
    tree_expression *retval = array_list;

    unwind_protect frame;

    frame.protect_var (discard_error_messages);
    frame.protect_var (discard_warning_messages);

    discard_error_messages = true;
    discard_warning_messages = true;

    if (array_list->all_elements_are_constant ())
      {
        try
          {
            tree_evaluator& tw
              = __get_evaluator__ ("finish_array_list");

            octave_value tmp = tw.evaluate (array_list);

            tree_constant *tc_retval
              = new tree_constant (tmp, array_list->line (),
                                   array_list->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&)
          {
            interpreter::recover_from_exception ();
          }
      }

    return retval;
  }

  // Finish building a matrix list.

  tree_expression *
  base_parser::finish_matrix (tree_matrix *m)
  {
    return (m
            ? finish_array_list (m)
            : new tree_constant (octave_null_matrix::instance));
  }

  // Finish building a cell list.

  tree_expression *
  base_parser::finish_cell (tree_cell *c)
  {
    return (c
            ? finish_array_list (c)
            : new tree_constant (octave_value (Cell ())));
  }

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

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

    list->append (stmt);

    return list;
  }

  void
  base_parser::bison_error (const std::string& str, int l, int c)
  {
    int err_line = l < 0 ? m_lexer.m_input_line_number : l;
    int err_col = c < 0 ? m_lexer.m_current_input_column - 1 : c;

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

    if (! curr_line.empty ())
      {
        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 ();
  }

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

    yypstate *pstate = static_cast<yypstate *> (m_parser_state);

    try
      {
        status = octave_pull_parse (pstate, *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 ());
      }

    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)
          {
            if (! eof && m_lexer.at_end_of_buffer ())
              {
                status = -1;
                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);

    if (status != 0)
      parse_error ("%s", m_parse_error_msg.c_str ());

    return status;
  }
}

static void
safe_fclose (FILE *f)
{
  if (f)
    fclose (static_cast<FILE *> (f));
}

static octave_value
parse_fcn_file (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,
                const std::string& warn_for)
{
  octave_value retval;

  octave::unwind_protect frame;

  octave_function *fcn_ptr = nullptr;

  // Open function file and parse.

  FILE *in_stream = octave::command_editor::get_input_stream ();

  frame.add_fcn (octave::command_editor::set_input_stream, in_stream);

  frame.add_fcn (octave::command_history::ignore_entries,
                 octave::command_history::ignoring_entries ());

  octave::command_history::ignore_entries ();

  FILE *ffile = nullptr;

  if (! full_file.empty ())
    ffile = std::fopen (full_file.c_str (), "rb");

  if (ffile)
    {
      frame.add_fcn (safe_fclose, ffile);

      octave::parser parser (ffile);

      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 status = parser.run ();

      fcn_ptr = parser.m_primary_fcn_ptr;

      if (status == 0)
        {
          if (parser.m_lexer.m_reading_classdef_file
              && parser.m_classdef_object)
            {
              // Convert parse tree for classdef object to
              // meta.class info (and stash it in the symbol
              // table?).  Return pointer to constructor?

              if (fcn_ptr)
                panic_impossible ();

              bool is_at_folder = ! dispatch_type.empty ();

              octave::interpreter& interp
                = octave::__get_interpreter__ ("parse_fcn_file");

              try
                {
                  fcn_ptr = parser.m_classdef_object->make_meta_class (interp, is_at_folder);
                }
              catch (const octave::execution_exception&)
                {
                  delete parser.m_classdef_object;
                  throw;
                }

              if (fcn_ptr)
                retval = octave_value (fcn_ptr);

              delete parser.m_classdef_object;

              parser.m_classdef_object = nullptr;
            }
          else if (fcn_ptr)
            {
              retval = octave_value (fcn_ptr);

              fcn_ptr->maybe_relocate_end ();

              if (parser.m_parsing_subfunctions)
                {
                  if (! parser.m_endfunction_found)
                    parser.m_subfunction_names.reverse ();

                  fcn_ptr->stash_subfunction_names (parser.m_subfunction_names);
                }
            }
        }
      else
        error ("parse error while reading file %s", full_file.c_str ());
    }
  else if (require_file)
    error ("no such file, '%s'", full_file.c_str ());
  else if (! warn_for.empty ())
    error ("%s: unable to open file '%s'", warn_for.c_str (),
           full_file.c_str ());

  return retval;
}

namespace octave
{
  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;

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

        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 ())
      {
        symbol_found = true;

        octave_value ov_fcn
          = parse_fcn_file (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);
  }

  std::string
  lookup_autoload (const std::string& nm)
  {
    std::string retval;

    typedef std::map<std::string, std::string>::const_iterator am_iter;

    am_iter p = autoload_map.find (nm);

    if (p != autoload_map.end ())
      {
        load_path& lp = __get_load_path__ ("lookup_autoload");

        retval = lp.find_file (p->second);
      }

    return retval;
  }

  string_vector
  autoloaded_functions (void)
  {
    string_vector names (autoload_map.size ());

    octave_idx_type i = 0;
    for (const auto& fcn_fname : autoload_map)
      names[i++] = fcn_fname.first;

    return names;
  }

  string_vector
  reverse_lookup_autoload (const std::string& nm)
  {
    string_vector names;

    for (const auto& fcn_fname : autoload_map)
      if (nm == fcn_fname.second)
        names.append (fcn_fname.first);

    return names;
  }

  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;

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

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

      dynamic_loader& dyn_loader
        = __get_dynamic_loader__ ("~octave_mex_function");

    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 (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 (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 */)
{
  octave_value retval;

  int nargin = args.length ();

  if (nargin == 1 || nargin > 3)
    print_usage ();

  if (nargin == 0)
    {
      Cell func_names (dim_vector (autoload_map.size (), 1));
      Cell file_names (dim_vector (autoload_map.size (), 1));

      octave_idx_type i = 0;
      for (const auto& fcn_fname : autoload_map)
        {
          func_names(i) = fcn_fname.first;
          file_names(i) = fcn_fname.second;

          i++;
        }

      octave_map m;

      m.assign ("function", func_names);
      m.assign ("file", file_names);

      retval = m;
    }
  else
    {
      string_vector argv = args.make_argv ("autoload");

      std::string nm = argv[2];

      if (! octave::sys::env::absolute_pathname (nm))
        {
          octave::call_stack& cs = interp.get_call_stack ();

          octave_user_code *fcn = cs.caller_user_code ();

          bool found = false;

          if (fcn)
            {
              std::string fname = fcn->fcn_file_name ();

              if (! fname.empty ())
                {
                  fname = octave::sys::env::make_absolute (fname);
                  fname = fname.substr (0, fname.find_last_of (octave::sys::file_ops::dir_sep_str ()) + 1);

                  octave::sys::file_stat fs (fname + nm);

                  if (fs.exists ())
                    {
                      nm = fname + nm;
                      found = true;
                    }
                }
            }
          if (! found)
            warning_with_id ("Octave:autoload-relative-file-name",
                             "autoload: '%s' is not an absolute filename",
                             nm.c_str ());
        }
      if (nargin == 2)
        autoload_map[argv[1]] = nm;
      else if (nargin == 3)
        {
          if (argv[3] != "remove")
            error_with_id ("Octave:invalid-input-arg",
                           "autoload: third argument can only be 'remove'");

          // Remove function from symbol table and autoload map.
          octave::symbol_table& symtab = interp.get_symbol_table ();
          symtab.clear_dld_function (argv[1]);
          autoload_map.erase (argv[1]);
        }
    }

  return retval;
}

namespace octave
{
  // 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, const std::string& warn_for)
  {
    // Map from absolute name of script file to recursion level.  We
    // use a map instead of simply placing a limit on recursion in the
    // source_file function so that two mutually recursive scripts
    // written as
    //
    //   foo1.m:
    //   ------
    //   foo2
    //
    //   foo2.m:
    //   ------
    //   foo1
    //
    // and called with
    //
    //   foo1
    //
    // (for example) will behave the same if they are written as
    //
    //   foo1.m:
    //   ------
    //   source ("foo2.m")
    //
    //   foo2.m:
    //   ------
    //   source ("foo1.m")
    //
    // and called with
    //
    //   source ("foo1.m")
    //
    // (for example).

    static std::map<std::string, int> source_call_depth;

    std::string file_full_name
      = sys::file_ops::tilde_expand (file_name);

    size_t pos
      = file_full_name.find_last_of (sys::file_ops::dir_sep_str ());

    std::string dir_name = file_full_name.substr (0, pos);

    file_full_name = sys::env::make_absolute (file_full_name);

    unwind_protect frame;

    if (source_call_depth.find (file_full_name) == source_call_depth.end ())
      source_call_depth[file_full_name] = -1;

    frame.protect_var (source_call_depth[file_full_name]);

    source_call_depth[file_full_name]++;

    tree_evaluator& tw = __get_evaluator__ ("source_file");

    if (source_call_depth[file_full_name] >= tw.max_recursion_depth ())
      error ("max_recursion_depth exceeded");

    if (! context.empty ())
      {
        call_stack& cs = __get_call_stack__ ("source_file");

        if (context == "caller")
          cs.goto_caller_frame ();
        else if (context == "base")
          cs.goto_base_frame ();
        else
          error ("source: context must be \"caller\" or \"base\"");

        frame.add_method (cs, &call_stack::pop);
      }

    // Find symbol name that would be in symbol_table, if it were loaded.
    size_t dir_end
      = file_name.find_last_of (sys::file_ops::dir_sep_chars ());
    dir_end = (dir_end == std::string::npos) ? 0 : dir_end + 1;

    size_t extension = file_name.find_last_of ('.');
    if (extension == std::string::npos)
      extension = file_name.length ();

    std::string symbol = file_name.substr (dir_end, extension - dir_end);
    std::string full_name = sys::canonicalize_file_name (file_name);

    // Check if this file is already loaded (or in the path)
    symbol_table& symtab = __get_symbol_table__ ("source_file");
    octave_value ov_code = symtab.find (symbol);

    // For compatibility with Matlab, accept both scripts and
    // functions.

    if (ov_code.is_user_code ())
      {
        octave_user_code *code = ov_code.user_code_value ();

        if (! code
            || (sys::canonicalize_file_name (code->fcn_file_name ())
                != full_name))
          {
            // Wrong file, so load it below.
            ov_code = octave_value ();
          }
      }
    else
      {
        // Not a script, so load it below.
        ov_code = octave_value ();
      }

    // If no symbol of this name, or the symbol is for a different
    // file, load.

    if (ov_code.is_undefined ())
      {
        try
          {
            ov_code = parse_fcn_file (file_full_name, file_name, dir_name,
                                      "", "", require_file, true, false,
                                      false, warn_for);
          }
        catch (execution_exception& e)
          {
            error (e, "source: error sourcing file '%s'",
                   file_full_name.c_str ());
          }
      }

    // Return or error if we don't have a valid script or function.

    if (ov_code.is_undefined ())
      return;

    if (! ov_code.is_user_code ())
      error ("source: %s is not a script", full_name.c_str ());

    if (verbose)
      {
        std::cout << "executing commands from " << full_name << " ... ";
        std::cout.flush ();
      }

    octave_user_code *code = ov_code.user_code_value ();

    code->call (tw, 0, octave_value_list ());

    if (verbose)
      std::cout << "done." << std::endl;
  }
}

DEFMETHOD (mfilename, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} mfilename ()
@deftypefnx {} {} mfilename ("fullpath")
@deftypefnx {} {} mfilename ("fullpathext")
Return the name of the currently executing file.

When called from outside an m-file 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.
@end deftypefn */)
{
  octave_value retval;

  int nargin = args.length ();

  if (nargin > 1)
    print_usage ();

  std::string arg;

  if (nargin == 1)
    arg = args(0).xstring_value ("mfilename: argument must be a string");

  std::string fname;

  octave::call_stack& cs = interp.get_call_stack ();

  octave_user_code *fcn = cs.caller_user_code ();

  if (fcn)
    {
      fname = fcn->fcn_file_name ();

      if (fname.empty ())
        fname = fcn->name ();
    }

  if (arg == "fullpathext")
    retval = fname;
  else
    {
      size_t dpos = fname.rfind (octave::sys::file_ops::dir_sep_char ());
      size_t epos = fname.rfind ('.');

      if (epos <= dpos)
        epos = std::string::npos;

      fname = (epos != std::string::npos) ? fname.substr (0, epos) : fname;

      if (arg == "fullpath")
        retval = fname;
      else
        retval = (dpos != std::string::npos) ? fname.substr (dpos+1) : fname;
    }

  return retval;
}

DEFUN (source, 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 */)
{
  octave_value_list retval;

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

  octave::source_file (file_name, context);

  return retval;
}

namespace octave
{
  //! 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 std::string& name, const octave_value_list& args, int nargout)
  {
    octave_value_list retval;

    symbol_table& symtab = __get_symbol_table__ ("feval");

    octave_value fcn = symtab.find_function (name, args);

    if (fcn.is_defined ())
      {
        tree_evaluator& tw = __get_evaluator__ ("feval");

        octave_function *of = fcn.function_value ();

        retval = of->call (tw, nargout, args);
      }
    else
      error ("feval: function '%s' not found", name.c_str ());

    return retval;
  }

  octave_value_list
  feval (octave_function *fcn, const octave_value_list& args, int nargout)
  {
    octave_value_list retval;

    if (fcn)
      {
        tree_evaluator& tw = __get_evaluator__ ("feval");

        retval = fcn->call (tw, nargout, args);
      }

    return retval;
  }

  octave_value_list
  feval (octave_value& val, const octave_value_list& args, int nargout)
  {
    if (val.is_function ())
      {
        return feval (val.function_value (), args, nargout);
      }
    else if (val.is_function_handle ())
      {
        // This covers function handles, inline functions, and anonymous
        //  functions.

        std::list<octave_value_list> arg_list;
        arg_list.push_back (args);

        return val.subsref ("(", arg_list, nargout);
      }
    else if (val.is_string ())
      {
        return feval (val.string_value (), args, nargout);
      }
    else
      error ("feval: first argument must be a string, inline function, or a function handle");

    return ovl ();
  }
}

static octave_value_list
get_feval_args (const octave_value_list& args)
{
  return args.slice (1, args.length () - 1, true);
}

namespace octave
{
  //! Evaluate an Octave function (built-in or interpreted) and return
  //! the list of result values.
  //!
  //! @param args The first element of @c args is the function to call.
  //!             It may be the name of the function as a string, a function
  //!             handle, or an inline function.  The remaining arguments are
  //!             passed 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 octave_value_list& args, int nargout)
  {
    if (args.length () > 0)
      {
        octave_value f_arg = args(0);

        octave_value_list tmp_args = get_feval_args (args);

        return feval (f_arg, tmp_args, nargout);
      }
    else
      error ("feval: first argument must be a string, inline function, or a function handle");

    return ovl ();
  }
}

DEFUN (feval, args, nargout,
       doc: /* -*- texinfo -*-
@deftypefn {} {} feval (@var{name}, @dots{})
Evaluate the function named @var{name}.

Any arguments after the first are passed as inputs to the named function.
For example,

@example
@group
feval ("acos", -1)
     @result{} 3.1416
@end group
@end example

@noindent
calls the function @code{acos} with the argument @samp{-1}.

The function @code{feval} can also be used with function handles of any sort
(@pxref{Function Handles}).  Historically, @code{feval} was the only way to
call user-supplied functions in strings, but function handles are now
preferred due to the cleaner syntax they offer.  For example,

@example
@group
@var{f} = @@exp;
feval (@var{f}, 1)
    @result{} 2.7183
@var{f} (1)
    @result{} 2.7183
@end group
@end example

@noindent
are equivalent ways to call the function referred to by @var{f}.  If it
cannot be predicted beforehand whether @var{f} is a function handle,
function name in a string, or inline function then @code{feval} can be used
instead.
@end deftypefn */)
{
  if (args.length () == 0)
    print_usage ();

  return octave::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"));

  octave::symbol_table& symtab = interp.get_symbol_table ();

  octave_value fcn = symtab.builtin_find (name);

  if (fcn.is_defined ())
    retval = octave::feval (fcn.function_value (), args.splice (0, 1), nargout);
  else
    error ("builtin: lookup for symbol '%s' failed", name.c_str ());

  return retval;
}

namespace octave
{
  octave_value_list
  eval_string (const std::string& eval_str, bool silent,
               int& parse_status, int nargout)
  {
    octave_value_list retval;

    parser parser (eval_str);

    do
      {
        parser.reset ();

        parse_status = parser.run ();

        if (parse_status == 0)
          {
            if (parser.m_stmt_list)
              {
                tree_statement *stmt = nullptr;

                tree_evaluator& tw = __get_evaluator__ ("eval_string");

                if (parser.m_stmt_list->length () == 1
                    && (stmt = parser.m_stmt_list->front ())
                    && stmt->is_expression ())
                  {
                    tree_expression *expr = stmt->expression ();

                    if (silent)
                      expr->set_print_flag (false);

                    bool do_bind_ans = false;

                    if (expr->is_identifier ())
                      {
                        octave::symbol_scope scope = tw.get_current_scope ();

                        octave::symbol_record::context_id context
                          = scope.current_context ();

                        tree_identifier *id
                          = dynamic_cast<tree_identifier *> (expr);

                        do_bind_ans = (! id->is_variable (context));
                      }
                    else
                      do_bind_ans = (! expr->is_assignment_expression ());

                    retval = tw.evaluate_n (expr, nargout);

                    if (do_bind_ans && ! retval.empty ())
                      tw.bind_ans (retval(0), expr->print_result ());

                    if (nargout == 0)
                      retval = octave_value_list ();
                  }
                else if (nargout == 0)
                  parser.m_stmt_list->accept (tw);
                else
                  error ("eval: invalid use of statement list");

                if (tree_return_command::returning
                    || tree_break_command::breaking
                    || tree_continue_command::continuing)
                  break;
              }
            else if (parser.m_lexer.m_end_of_input)
              break;
          }
      }
    while (parse_status == 0);

    return retval;
  }

  octave_value
  eval_string (const std::string& eval_str, bool silent, int& parse_status)
  {
    octave_value retval;

    octave_value_list tmp = eval_string (eval_str, silent, parse_status, 1);

    if (! tmp.empty ())
      retval = tmp(0);

    return retval;
  }

  static octave_value_list
  eval_string (const octave_value& arg, bool silent, int& parse_status,
               int nargout)
  {
    std::string s = arg.xstring_value ("eval: expecting std::string argument");

    return eval_string (s, silent, parse_status, nargout);
  }

  void
  cleanup_statement_list (tree_statement_list **lst)
  {
    if (*lst)
      {
        delete *lst;
        *lst = nullptr;
      }
  }
}

DEFUN (eval, 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 */)
{
  octave_value_list retval;

  int nargin = args.length ();

  if (nargin == 0)
    print_usage ();

  octave::unwind_protect frame;

  if (nargin > 1)
    {
      frame.protect_var (buffer_error_messages);
      buffer_error_messages++;
    }

  int parse_status = 0;

  bool execution_error = false;

  octave_value_list tmp;

  try
    {
      tmp = octave::eval_string (args(0), nargout > 0, parse_status, nargout);
    }
  catch (const octave::execution_exception&)
    {
      octave::interpreter::recover_from_exception ();

      execution_error = true;
    }

  if (nargin > 1 && (parse_status != 0 || execution_error))
    {
      // Set up for letting the user print any messages from
      // errors that occurred in the first part of this eval().

      buffer_error_messages--;

      tmp = octave::eval_string (args(1), nargout > 0, parse_status, nargout);

      if (nargout > 0)
        retval = tmp;
    }
  else
    {
      if (nargout > 0)
        retval = tmp;

      // FIXME: we should really be rethrowing whatever exception occurred,
      // not just throwing an execution exception.
      if (execution_error)
        octave_throw_execution_exception ();
    }

  return retval;
}

/*

%!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)

% bug #35645
%!test
%! [a,] = gcd (1,2);
%! [a,b,] = gcd (1, 2);

%!error eval ("switch = 13;")

*/

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 */)
{
  octave_value_list retval;

  if (args.length () != 3)
    print_usage ();

  std::string context = args(0).xstring_value ("assignin: CONTEXT must be a string");

  octave::unwind_protect frame;

  octave::call_stack& cs = interp.get_call_stack ();

  if (context == "caller")
    cs.goto_caller_frame ();
  else if (context == "base")
    cs.goto_base_frame ();
  else
    error ("assignin: CONTEXT must be \"caller\" or \"base\"");

  frame.add_method (cs, &octave::call_stack::pop);

  std::string nm = args(1).xstring_value ("assignin: VARNAME must be a string");

  if (valid_identifier (nm))
    {
      // Put the check here so that we don't slow down assignments
      // generally.  Any that go through Octave's parser should have
      // already been checked.

      if (octave::is_keyword (nm))
        error ("assignin: invalid assignment to keyword '%s'", nm.c_str ());

      octave::symbol_scope scope = interp.get_current_scope ();

      if (scope)
        scope.assign (nm, args(2));
    }
  else
    error ("assignin: invalid variable name in argument VARNAME");

  return retval;
}

/*

%!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 */)
{
  octave_value_list retval;

  int nargin = args.length ();

  if (nargin < 2)
    print_usage ();

  std::string context = args(0).xstring_value ("evalin: CONTEXT must be a string");

  octave::unwind_protect frame;

  octave::call_stack& cs = interp.get_call_stack ();

  if (context == "caller")
    cs.goto_caller_frame ();
  else if (context == "base")
    cs.goto_base_frame ();
  else
    error ("evalin: CONTEXT must be \"caller\" or \"base\"");

  frame.add_method (cs, &octave::call_stack::pop);

  if (nargin > 2)
    {
      frame.protect_var (buffer_error_messages);
      buffer_error_messages++;
    }

  int parse_status = 0;

  bool execution_error = false;

  octave_value_list tmp;

  try
    {
      tmp = octave::eval_string (args(1), nargout > 0,
                                 parse_status, nargout);
    }
  catch (const octave::execution_exception&)
    {
      octave::interpreter::recover_from_exception ();

      execution_error = true;
    }

  if (nargin > 2 && (parse_status != 0 || execution_error))
    {
      // Set up for letting the user print any messages from
      // errors that occurred in the first part of this eval().

      buffer_error_messages--;

      tmp = octave::eval_string (args(2), nargout > 0,
                                 parse_status, nargout);

      retval = (nargout > 0) ? tmp : octave_value_list ();
    }
  else
    {
      if (nargout > 0)
        retval = tmp;

      // FIXME: we should really be rethrowing whatever
      // exception occurred, not just throwing an
      // execution exception.
      if (execution_error)
        octave_throw_execution_exception ();
    }

  return retval;
}

static void
maybe_print_last_error_message (bool *doit)
{
  if (doit && *doit)
    // Print error message again, which was lost because of the stderr buffer
    // Note: this keeps error_state and last_error_stack intact
    message_with_id ("error", last_error_id ().c_str (),
                     last_error_message ().c_str ());
}

static void
restore_octave_stdout (std::streambuf *buf)
{
  octave_stdout.flush ();
  octave_stdout.rdbuf (buf);
}

static void
restore_octave_stderr (std::streambuf *buf)
{
  std::cerr.flush ();
  std::cerr.rdbuf (buf);
}

DEFUN (evalc, 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 ();

  // redirect stdout/stderr to capturing buffer
  std::ostringstream buffer;

  std::ostream& out_stream = octave_stdout;
  std::ostream& err_stream = std::cerr;

  out_stream.flush ();
  err_stream.flush ();

  std::streambuf* old_out_buf = out_stream.rdbuf (buffer.rdbuf ());
  std::streambuf* old_err_buf = err_stream.rdbuf (buffer.rdbuf ());

  bool eval_error_occurred = true;

  octave::unwind_protect frame;

  frame.add_fcn (maybe_print_last_error_message, &eval_error_occurred);
  frame.add_fcn (restore_octave_stdout, old_out_buf);
  frame.add_fcn (restore_octave_stderr, old_err_buf);

  // call standard eval function
  octave_value_list retval;
  int eval_nargout = std::max (0, nargout - 1);

  retval = Feval (args, eval_nargout);
  eval_error_occurred = false;

  retval.prepend (buffer.str ());
  return retval;
}

/*

%!assert (evalc ("1"), "ans =  1\n")
%!assert (evalc ("1;"), "")

%!test
%! [s, y] = evalc ("1");
%! assert (s, "");
%! assert (y, 1);

%!test
%! [s, y] = evalc ("1;");
%! assert (s, "");
%! assert (y, 1);

%!test
%! assert (evalc ("y = 2"), "y =  2\n");
%! assert (y, 2);

%!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
%! [s, a, b] = evalc ("__f_evalc ()");
%! assert (s, "foobar 3.1416\n");
%! assert (a, 1);
%! assert (b, 2);

%!error <foo> (evalc ("error ('foo')"))
%!error <bar> (evalc ("error ('foo')", "error ('bar')"))

%!test
%! warning ("off", "quiet", "local");
%! assert (evalc ("warning ('foo')"), "warning: foo\n");

%!test
%! warning ("off", "quiet", "local");
%! assert (evalc ("error ('foo')", "warning ('bar')"), "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;
}

DEFUN (__parse_file__, 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
      = octave::sys::file_ops::tilde_expand (file);

  full_file = octave::sys::env::make_absolute (full_file);

  std::string dir_name;

  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 = octave::sys::env::base_pathname (file);
      file = file.substr (0, file.find_last_of ('.'));

      size_t pos = file.find_last_of (octave::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 (full_file, file, dir_name, "", "", true, false,
                      false, false, "__parse_file__");

  return retval;
}
