Mercurial > jwe > octave
view libinterp/parse-tree/oct-parse.yy @ 30113:3efce22b7350
eliminate duplicate parent function name info in symbol scope objects
* symscope.h, symscope.cc (symbol_scope_rep::m_parent_fcn_names):
Delete member variable.
(symbol_scope_rep::cache_parent_fcn_names):
Delete function and all uses.
(symbol_scope_rep::parent_fcn_names): Generate list of parent function
names from parent scope info as needed.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Thu, 02 Sep 2021 11:20:53 -0400 |
parents | 67994b92a613 |
children | a9e3e1c96c47 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 1993-2021 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // This file is part of Octave. // // Octave is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Octave is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Octave; see the file COPYING. If not, see // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// // Parser for Octave. // C decarations. %{ #define YYDEBUG 1 #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <cassert> #include <cstdio> #include <cstdlib> #include <iostream> #include <map> #include <sstream> #include "Matrix.h" #include "cmd-edit.h" #include "cmd-hist.h" #include "file-ops.h" #include "file-stat.h" #include "oct-env.h" #include "oct-time.h" #include "quit.h" #include "Cell.h" #include "anon-fcn-validator.h" #include "builtin-defun-decls.h" #include "defun.h" #include "dynamic-ld.h" #include "error.h" #include "input.h" #include "interpreter-private.h" #include "interpreter.h" #include "lex.h" #include "load-path.h" #include "lo-sysdep.h" #include "oct-hist.h" #include "oct-map.h" #include "ov-classdef.h" #include "ov-fcn-handle.h" #include "ov-usr-fcn.h" #include "ov-null-mat.h" #include "pager.h" #include "parse.h" #include "pt-all.h" #include "pt-eval.h" #include "symtab.h" #include "token.h" #include "unwind-prot.h" #include "utils.h" #include "variables.h" // oct-parse.h must be included after pt-all.h #include "oct-parse.h" extern int octave_lex (YYSTYPE *, void *); // Forward declarations for some functions defined at the bottom of // the file. static void yyerror (octave::base_parser& parser, const char *s); #define lexer (parser.get_lexer ()) #define scanner lexer.m_scanner // Previous versions of Octave used Bison's YYUSE macro to avoid // warnings about unused values in rules. But that Bison macro was // apparently never intended to be public. So define our own. All we // need to do is mention the symantic value somewhere in the rule. It // doesn't actually need to be used to avoid the Bison warning, so just // define this macro to discard its parameter. #define OCTAVE_YYUSE(X) #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC) // Disable this warning for code that is generated by Bison, // including grammar rules. Push the current state so we can // restore the warning state prior to functions we define at // the bottom of the file. # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" #endif %} // Bison declarations. // The grammar currently has 9 shift/reduce conflicts. Ensure that // we notice if that number changes. %expect 9 // We are using the pure parser interface and the reentrant lexer // interface but the Octave parser and lexer are NOT properly // reentrant because both still use many global variables. It should be // safe to create a parser object and call it while another parser // object is active (to parse a callback function while the main // interactive parser is waiting for input, for example) if you take // care to properly save and restore (typically with an unwind_protect // object) relevant global values before and after the nested call. %define api.pure // No spaces inside the braces for the prefix and push-pull definitions! %define api.prefix {octave_} %define api.push-pull both %parse-param { octave::base_parser& parser } %lex-param { void *lexer.scanner } %union { int dummy_type; // The type of the basic tokens returned by the lexer. octave::token *tok_val; // Comment strings that we need to deal with mid-rule. octave::comment_list *comment_type; // Types for the nonterminals we generate. char punct_type; octave::tree *tree_type; octave::tree_matrix *tree_matrix_type; octave::tree_cell *tree_cell_type; octave::tree_expression *tree_expression_type; octave::tree_constant *tree_constant_type; octave::tree_fcn_handle *tree_fcn_handle_type; octave::tree_superclass_ref *tree_superclass_ref_type; octave::tree_metaclass_query *tree_metaclass_query_type; octave::tree_function_def *tree_function_def_type; octave::tree_anon_fcn_handle *tree_anon_fcn_handle_type; octave::tree_identifier *tree_identifier_type; octave::tree_index_expression *tree_index_expression_type; octave::tree_colon_expression *tree_colon_expression_type; octave::tree_argument_list *tree_argument_list_type; octave::tree_parameter_list *tree_parameter_list_type; octave::tree_command *tree_command_type; octave::tree_if_command *tree_if_command_type; octave::tree_if_clause *tree_if_clause_type; octave::tree_if_command_list *tree_if_command_list_type; octave::tree_switch_command *tree_switch_command_type; octave::tree_switch_case *tree_switch_case_type; octave::tree_switch_case_list *tree_switch_case_list_type; octave::tree_decl_elt *tree_decl_elt_type; octave::tree_decl_init_list *tree_decl_init_list_type; octave::tree_decl_command *tree_decl_command_type; octave::tree_statement *tree_statement_type; octave::tree_statement_list *tree_statement_list_type; octave::tree_arguments_block *tree_arguments_block_type; octave::tree_args_block_attribute_list *tree_args_block_attribute_list_type; octave::tree_args_block_validation_list *tree_args_block_validation_list_type; octave::tree_arg_size_spec *tree_arg_size_spec_type; octave::tree_arg_validation *tree_arg_validation_type; octave::tree_arg_validation_fcns *tree_arg_validation_fcns_type; octave_user_function *octave_user_function_type; octave::tree_classdef *tree_classdef_type; octave::tree_classdef_attribute* tree_classdef_attribute_type; octave::tree_classdef_attribute_list* tree_classdef_attribute_list_type; octave::tree_classdef_superclass* tree_classdef_superclass_type; octave::tree_classdef_superclass_list* tree_classdef_superclass_list_type; octave::tree_classdef_body* tree_classdef_body_type; octave::tree_classdef_property* tree_classdef_property_type; octave::tree_classdef_property_list* tree_classdef_property_list_type; octave::tree_classdef_properties_block* tree_classdef_properties_block_type; octave::tree_classdef_methods_list* tree_classdef_methods_list_type; octave::tree_classdef_methods_block* tree_classdef_methods_block_type; octave::tree_classdef_event* tree_classdef_event_type; octave::tree_classdef_events_list* tree_classdef_events_list_type; octave::tree_classdef_events_block* tree_classdef_events_block_type; octave::tree_classdef_enum* tree_classdef_enum_type; octave::tree_classdef_enum_list* tree_classdef_enum_list_type; octave::tree_classdef_enum_block* tree_classdef_enum_block_type; } // Tokens with line and column information. %token <tok_val> '=' ':' '-' '+' '*' '/' %token <tok_val> '(' ')' '[' ']' '{' '}' '.' '@' %token <tok_val> ',' ';' '\n' %token <tok_val> ADD_EQ SUB_EQ MUL_EQ DIV_EQ LEFTDIV_EQ POW_EQ %token <tok_val> EMUL_EQ EDIV_EQ ELEFTDIV_EQ EPOW_EQ AND_EQ OR_EQ %token <tok_val> EXPR_AND_AND EXPR_OR_OR %token <tok_val> EXPR_AND EXPR_OR EXPR_NOT %token <tok_val> EXPR_LT EXPR_LE EXPR_EQ EXPR_NE EXPR_GE EXPR_GT %token <tok_val> LEFTDIV EMUL EDIV ELEFTDIV EPLUS EMINUS %token <tok_val> HERMITIAN TRANSPOSE %token <tok_val> PLUS_PLUS MINUS_MINUS POW EPOW %token <tok_val> NUMBER %token <tok_val> STRUCT_ELT %token <tok_val> NAME %token <tok_val> END %token <tok_val> DQ_STRING SQ_STRING %token <tok_val> FOR PARFOR WHILE DO UNTIL %token <tok_val> SPMD %token <tok_val> IF ELSEIF ELSE %token <tok_val> SWITCH CASE OTHERWISE %token <tok_val> BREAK CONTINUE FUNC_RET %token <tok_val> UNWIND CLEANUP %token <tok_val> TRY CATCH %token <tok_val> GLOBAL PERSISTENT %token <tok_val> FCN_HANDLE %token <tok_val> CLASSDEF %token <tok_val> PROPERTIES METHODS EVENTS ENUMERATION %token <tok_val> METAQUERY %token <tok_val> SUPERCLASSREF %token <tok_val> FQ_IDENT %token <tok_val> GET SET %token <tok_val> FCN %token <tok_val> ARGUMENTS %token <tok_val> LEXICAL_ERROR %token <tok_val> END_OF_INPUT // Other tokens. %token <dummy_type> INPUT_FILE // %token VARARGIN VARARGOUT // Nonterminals we construct. %type <dummy_type> indirect_ref_op %type <dummy_type> push_fcn_symtab push_script_symtab begin_file %type <dummy_type> param_list_beg param_list_end stmt_begin anon_fcn_begin %type <dummy_type> parsing_local_fcns parse_error at_first_executable_stmt %type <comment_type> stash_comment %type <tok_val> function_beg classdef_beg arguments_beg %type <tok_val> properties_beg methods_beg events_beg enumeration_beg %type <punct_type> sep_no_nl opt_sep_no_nl nl opt_nl sep opt_sep %type <tree_type> input %type <tree_constant_type> string constant magic_colon %type <tree_anon_fcn_handle_type> anon_fcn_handle %type <tree_fcn_handle_type> fcn_handle %type <tree_matrix_type> matrix_rows %type <tree_cell_type> cell_rows %type <tree_expression_type> matrix cell %type <tree_expression_type> primary_expr oper_expr power_expr %type <tree_expression_type> simple_expr colon_expr assign_expr expression %type <tree_expression_type> arg_name %type <tree_identifier_type> identifier fcn_name magic_tilde %type <tree_superclass_ref_type> superclass_identifier %type <tree_metaclass_query_type> meta_identifier %type <tree_index_expression_type> word_list_cmd %type <tree_argument_list_type> arg_list word_list assign_lhs %type <tree_argument_list_type> cell_or_matrix_row %type <tree_parameter_list_type> opt_param_list param_list %type <tree_parameter_list_type> param_list1 param_list2 %type <tree_parameter_list_type> return_list return_list1 %type <tree_command_type> command select_command loop_command %type <tree_command_type> jump_command spmd_command except_command %type <tree_function_def_type> function %type <tree_classdef_type> classdef %type <tree_command_type> file %type <tree_if_command_type> if_command %type <tree_if_clause_type> elseif_clause else_clause %type <tree_if_command_list_type> if_cmd_list1 if_cmd_list %type <tree_switch_command_type> switch_command %type <tree_switch_case_type> switch_case default_case %type <tree_switch_case_list_type> case_list1 case_list %type <tree_decl_elt_type> decl_elt param_list_elt %type <tree_decl_init_list_type> decl_init_list %type <tree_decl_command_type> declaration %type <tree_statement_type> statement function_end %type <tree_statement_list_type> simple_list simple_list1 list list1 %type <tree_statement_list_type> opt_list function_body function_body1 %type <tree_statement_list_type> opt_fcn_list fcn_list fcn_list1 %type <tree_classdef_attribute_type> attr %type <tree_classdef_attribute_list_type> attr_list attr_list1 %type <tree_classdef_superclass_type> superclass %type <tree_classdef_superclass_list_type> superclass_list superclass_list1 %type <tree_classdef_body_type> class_body class_body1 %type <tree_classdef_property_type> class_property %type <tree_classdef_property_list_type> property_list property_list1 %type <tree_classdef_properties_block_type> properties_block %type <tree_classdef_methods_list_type> methods_list methods_list1 %type <tree_classdef_methods_block_type> methods_block %type <tree_classdef_event_type> class_event %type <tree_classdef_events_list_type> events_list events_list1 %type <tree_classdef_events_block_type> events_block %type <tree_classdef_enum_type> class_enum %type <tree_classdef_enum_list_type> enum_list enum_list1 %type <tree_classdef_enum_block_type> enum_block %type <tree_function_def_type> method_decl method %type <tree_arguments_block_type> arguments_block %type <tree_args_block_attribute_list_type> args_attr_list %type <tree_args_block_validation_list_type> args_validation_list %type <tree_arg_validation_type> arg_validation %type <tree_arg_size_spec_type> size_spec %type <tree_identifier_type> class_name %type <tree_arg_validation_fcns_type> validation_fcns %type <tree_expression_type> default_value %type <octave_user_function_type> method_decl1 // Precedence and associativity. %right '=' ADD_EQ SUB_EQ MUL_EQ DIV_EQ LEFTDIV_EQ POW_EQ EMUL_EQ EDIV_EQ ELEFTDIV_EQ EPOW_EQ OR_EQ AND_EQ %left EXPR_OR_OR %left EXPR_AND_AND %left EXPR_OR %left EXPR_AND %left EXPR_LT EXPR_LE EXPR_EQ EXPR_NE EXPR_GE EXPR_GT %left ':' %left '-' '+' 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 separately and separators are just characters. The // remaining items are dynamically allocated parse tree objects that // must be deleted. Use the wildcard case (<*>) to detect unhandled // cases (for example, a new semantic type is added but not handled // here). %destructor { } <tok_val> %destructor { } <punct_type> %destructor { } <comment_type> %destructor { } <> %destructor { delete $$; } <tree_type> %destructor { delete $$; } <tree_matrix_type> %destructor { delete $$; } <tree_cell_type> %destructor { delete $$; } <tree_expression_type> %destructor { delete $$; } <tree_constant_type> %destructor { delete $$; } <tree_fcn_handle_type> %destructor { delete $$; } <tree_superclass_ref_type> %destructor { delete $$; } <tree_metaclass_query_type> %destructor { delete $$; } <tree_function_def_type> %destructor { delete $$; } <tree_anon_fcn_handle_type> %destructor { delete $$; } <tree_identifier_type> %destructor { delete $$; } <tree_index_expression_type> %destructor { delete $$; } <tree_argument_list_type> %destructor { delete $$; } <tree_parameter_list_type> %destructor { delete $$; } <tree_command_type> %destructor { delete $$; } <tree_if_command_type> %destructor { delete $$; } <tree_if_clause_type> %destructor { delete $$; } <tree_if_command_list_type> %destructor { delete $$; } <tree_switch_command_type> %destructor { delete $$; } <tree_switch_case_type> %destructor { delete $$; } <tree_switch_case_list_type> %destructor { delete $$; } <tree_decl_elt_type> %destructor { delete $$; } <tree_decl_init_list_type> %destructor { delete $$; } <tree_decl_command_type> %destructor { delete $$; } <tree_statement_type> %destructor { delete $$; } <tree_statement_list_type> %destructor { delete $$; } <tree_arguments_block_type> %destructor { delete $$; } <tree_args_block_attribute_list_type> %destructor { delete $$; } <tree_args_block_validation_list_type> %destructor { delete $$; } <tree_arg_validation_type> %destructor { delete $$; } <tree_arg_size_spec_type> %destructor { delete $$; } <tree_arg_validation_fcns_type> %destructor { delete $$; } <octave_user_function_type> %destructor { delete $$; } <tree_classdef_type> %destructor { delete $$; } <tree_classdef_attribute_type> %destructor { delete $$; } <tree_classdef_attribute_list_type> %destructor { delete $$; } <tree_classdef_superclass_type> %destructor { delete $$; } <tree_classdef_superclass_list_type> %destructor { delete $$; } <tree_classdef_body_type> %destructor { delete $$; } <tree_classdef_property_type> %destructor { delete $$; } <tree_classdef_property_list_type> %destructor { delete $$; } <tree_classdef_properties_block_type> %destructor { delete $$; } <tree_classdef_methods_list_type> %destructor { delete $$; } <tree_classdef_methods_block_type> %destructor { delete $$; } <tree_classdef_event_type> %destructor { delete $$; } <tree_classdef_events_list_type> %destructor { delete $$; } <tree_classdef_events_block_type> %destructor { delete $$; } <tree_classdef_enum_type> %destructor { delete $$; } <tree_classdef_enum_list_type> %destructor { delete $$; } <tree_classdef_enum_block_type> // Defining a generic destructor generates a warning if destructors are // already explicitly declared for all types. // // %destructor { // warning_with_id // ("Octave:parser-destructor", // "possible memory leak in cleanup following parse error"); // } <*> // Where to start. %start input %% // ============================== // Statements and statement lists // ============================== input : simple_list '\n' { OCTAVE_YYUSE ($2); $$ = nullptr; if (! parser.finish_input ($1)) YYABORT; else YYACCEPT; } | simple_list END_OF_INPUT { OCTAVE_YYUSE ($2); $$ = nullptr; if (! parser.finish_input ($1, true)) YYABORT; else YYACCEPT; } | parse_error { $$ = nullptr; YYABORT; } ; simple_list : opt_sep_no_nl { OCTAVE_YYUSE ($1); $$ = nullptr; } | simple_list1 opt_sep_no_nl { $$ = parser.set_stmt_print_flag ($1, $2, false); } ; simple_list1 : statement { $$ = parser.make_statement_list ($1); } | simple_list1 sep_no_nl statement { $$ = parser.append_statement_list ($1, $2, $3, false); } ; opt_list : // empty { $$ = 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 { OCTAVE_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; } $$->mark_word_list_cmd (); } ; word_list : string { $$ = new octave::tree_argument_list ($1); } | word_list string { $1->append ($2); $$ = $1; } ; // =========== // Expressions // =========== identifier : NAME { // Find the token in the symbol table. octave::symbol_scope scope = lexer.m_symtab_context.curr_scope (); std::string nm = $1->text (); octave::symbol_record sr = (scope ? scope.insert (nm) : octave::symbol_record (nm)); $$ = new octave::tree_identifier (sr, $1->line (), $1->column ()); } ; superclass_identifier : SUPERCLASSREF { std::string meth = $1->superclass_method_name (); std::string cls = $1->superclass_class_name (); $$ = new octave::tree_superclass_ref (meth, cls, $1->line (), $1->column ()); } ; meta_identifier : METAQUERY { std::string cls = $1->text (); $$ = new octave::tree_metaclass_query (cls, $1->line (), $1->column ()); } ; string : DQ_STRING { $$ = parser.make_constant (DQ_STRING, $1); } | SQ_STRING { $$ = parser.make_constant (SQ_STRING, $1); } ; constant : NUMBER { $$ = parser.make_constant (NUMBER, $1); } | string { $$ = $1; } ; matrix : '[' matrix_rows ']' { $$ = parser.finish_matrix ($2, $1, $3); } ; matrix_rows : cell_or_matrix_row { $$ = $1 ? new octave::tree_matrix ($1) : nullptr; } | matrix_rows ';' cell_or_matrix_row { OCTAVE_YYUSE ($2); if ($1) { if ($3) $1->append ($3); $$ = $1; } else $$ = $3 ? new octave::tree_matrix ($3) : nullptr; } ; cell : '{' cell_rows '}' { $$ = parser.finish_cell ($2, $1, $3); } ; cell_rows : cell_or_matrix_row { $$ = $1 ? new octave::tree_cell ($1) : nullptr; } | cell_rows ';' cell_or_matrix_row { OCTAVE_YYUSE ($2); 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. // FIXME: is tree_argument_list the best object for this purpose, or // should we have a separate one intended specifically to represent the // list of objects that make up elements in cell and matrix expressions? cell_or_matrix_row : // empty { $$ = nullptr; } | ',' { OCTAVE_YYUSE ($1); $$ = nullptr; } | arg_list { $$ = $1; } | arg_list ',' { OCTAVE_YYUSE ($2); $$ = $1; } | ',' arg_list { OCTAVE_YYUSE ($1); $$ = $2; } | ',' arg_list ',' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); $$ = $2; } ; fcn_handle : FCN_HANDLE { $$ = parser.make_fcn_handle ($1); } ; // Note that we are deliberately not setting the beginning of statement // flag after recognizing the parameter list because we don't want to // accept word list commands in anonymous function bodies. anon_fcn_handle : '@' param_list anon_fcn_begin expression { $$ = parser.make_anon_fcn_handle ($2, $4, $1->beg_pos ()); if (! $$) { // make_anon_fcn_handle deleted $2 and $4. YYABORT; } lexer.m_parsing_anon_fcn_body = false; lexer.m_nesting_level.remove (); } | '@' param_list anon_fcn_begin error { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($2); lexer.m_parsing_anon_fcn_body = false; $$ = nullptr; parser.bison_error ("anonymous function bodies must be single expressions"); YYABORT; } ; primary_expr : identifier { $$ = $1; } | constant { $$ = $1; } | fcn_handle { $$ = $1; } | matrix { lexer.m_looking_at_matrix_or_assign_lhs = false; $$ = $1; } | cell { $$ = $1; } | meta_identifier { $$ = $1; } | superclass_identifier { $$ = $1; } | '(' expression ')' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); $$ = $2->mark_in_parens (); } ; magic_colon : ':' { OCTAVE_YYUSE ($1); octave_value tmp (octave_value::magic_colon_t); $$ = new octave::tree_constant (tmp); } ; magic_tilde : EXPR_NOT { OCTAVE_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 { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } | arg_list ',' magic_tilde { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } | arg_list ',' expression { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; indirect_ref_op : '.' { OCTAVE_YYUSE ($1); $$ = 0; lexer.m_looking_at_indirect_ref = true; } ; oper_expr : primary_expr { $$ = $1; } | oper_expr PLUS_PLUS { $$ = parser.make_postfix_op (PLUS_PLUS, $1, $2); } | oper_expr MINUS_MINUS { $$ = parser.make_postfix_op (MINUS_MINUS, $1, $2); } | oper_expr '(' ')' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($3); $$ = parser.make_index_expression ($1, nullptr, '('); if (! $$) { // make_index_expression deleted $1. YYABORT; } } | oper_expr '(' arg_list ')' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($4); $$ = parser.make_index_expression ($1, $3, '('); if (! $$) { // make_index_expression deleted $1 and $3. YYABORT; } } | oper_expr '{' '}' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($3); $$ = parser.make_index_expression ($1, nullptr, '{'); if (! $$) { // make_index_expression deleted $1. YYABORT; } } | oper_expr '{' arg_list '}' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($4); $$ = parser.make_index_expression ($1, $3, '{'); if (! $$) { // make_index_expression deleted $1 and $3. YYABORT; } } | oper_expr HERMITIAN { $$ = parser.make_postfix_op (HERMITIAN, $1, $2); } | oper_expr TRANSPOSE { $$ = parser.make_postfix_op (TRANSPOSE, $1, $2); } | oper_expr indirect_ref_op STRUCT_ELT { $$ = parser.make_indirect_ref ($1, $3->text ()); } | oper_expr indirect_ref_op '(' expression ')' { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); $$ = parser.make_indirect_ref ($1, $4); } | PLUS_PLUS oper_expr %prec UNARY { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); } | MINUS_MINUS oper_expr %prec UNARY { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); } | EXPR_NOT oper_expr %prec UNARY { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); } | '+' oper_expr %prec UNARY { $$ = parser.make_prefix_op ('+', $2, $1); } | '-' oper_expr %prec UNARY { $$ = parser.make_prefix_op ('-', $2, $1); } | oper_expr POW power_expr { $$ = parser.make_binary_op (POW, $1, $2, $3); } | oper_expr EPOW power_expr { $$ = parser.make_binary_op (EPOW, $1, $2, $3); } | oper_expr '+' oper_expr { $$ = parser.make_binary_op ('+', $1, $2, $3); } | oper_expr '-' oper_expr { $$ = parser.make_binary_op ('-', $1, $2, $3); } | oper_expr '*' oper_expr { $$ = parser.make_binary_op ('*', $1, $2, $3); } | oper_expr '/' oper_expr { $$ = parser.make_binary_op ('/', $1, $2, $3); } | oper_expr 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 '(' ')' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($3); $$ = parser.make_index_expression ($1, nullptr, '('); if (! $$) { // make_index_expression deleted $1. YYABORT; } } | power_expr '(' arg_list ')' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($4); $$ = parser.make_index_expression ($1, $3, '('); if (! $$) { // make_index_expression deleted $1 and $3. YYABORT; } } | power_expr '{' '}' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($3); $$ = parser.make_index_expression ($1, nullptr, '{'); if (! $$) { // make_index_expression deleted $1. YYABORT; } } | power_expr '{' arg_list '}' { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($4); $$ = parser.make_index_expression ($1, $3, '{'); if (! $$) { // make_index_expression deleted $1 and $3. YYABORT; } } | power_expr indirect_ref_op STRUCT_ELT { $$ = parser.make_indirect_ref ($1, $3->text ()); } | power_expr indirect_ref_op '(' expression ')' { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); $$ = parser.make_indirect_ref ($1, $4); } | PLUS_PLUS power_expr %prec POW { $$ = parser.make_prefix_op (PLUS_PLUS, $2, $1); } | MINUS_MINUS power_expr %prec POW { $$ = parser.make_prefix_op (MINUS_MINUS, $2, $1); } | EXPR_NOT power_expr %prec POW { $$ = parser.make_prefix_op (EXPR_NOT, $2, $1); } | '+' power_expr %prec POW { $$ = parser.make_prefix_op ('+', $2, $1); } | '-' power_expr %prec POW { $$ = parser.make_prefix_op ('-', $2, $1); } ; colon_expr : oper_expr ':' oper_expr { OCTAVE_YYUSE ($2); $$ = parser.make_colon_expression ($1, $3); if (! $$) { // finish_colon_expression deleted $1 and $3. YYABORT; } } | oper_expr ':' oper_expr ':' oper_expr { OCTAVE_YYUSE ($2); OCTAVE_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); } ; expression : simple_expr { if ($1 && ($1->is_matrix () || $1->iscell ())) { if (parser.validate_array_list ($1)) $$ = $1; else { delete $1; YYABORT; } } else $$ = $1; } | assign_expr { if (! $1) YYABORT; $$ = $1; } | anon_fcn_handle { $$ = $1; } ; // ================================================ // Commands, declarations, and function definitions // ================================================ command : declaration { $$ = $1; } | select_command { $$ = $1; } | loop_command { $$ = $1; } | jump_command { $$ = $1; } | spmd_command { $$ = $1; } | except_command { $$ = $1; } | function { $$ = $1; } | file { $$ = $1; } ; // ====================== // Declaration statements // ====================== declaration : GLOBAL decl_init_list { $$ = parser.make_decl_command (GLOBAL, $1, $2); lexer.m_looking_at_decl_list = false; } | PERSISTENT decl_init_list { $$ = parser.make_decl_command (PERSISTENT, $1, $2); lexer.m_looking_at_decl_list = false; } ; decl_init_list : decl_elt { $$ = new octave::tree_decl_init_list ($1); } | decl_init_list decl_elt { $1->append ($2); $$ = $1; } ; decl_elt : identifier { $$ = new octave::tree_decl_elt ($1); } | identifier '=' expression { OCTAVE_YYUSE ($2); $$ = new octave::tree_decl_elt ($1, $3); } ; // ==================== // Selection statements // ==================== select_command : if_command { $$ = $1; } | switch_command { $$ = $1; } ; // ============ // If statement // ============ if_command : IF stash_comment if_cmd_list END { if (! ($$ = parser.finish_if_command ($1, $3, $4, $2))) { // finish_if_command deleted $3. YYABORT; } } ; if_cmd_list : if_cmd_list1 { $$ = $1; } | if_cmd_list1 else_clause { $1->append ($2); $$ = $1; } ; if_cmd_list1 : expression stmt_begin opt_sep opt_list { OCTAVE_YYUSE ($3); $1->mark_braindead_shortcircuit (); $$ = parser.start_if_command ($1, $4); } | if_cmd_list1 elseif_clause { $1->append ($2); $$ = $1; } ; elseif_clause : ELSEIF stash_comment opt_sep expression stmt_begin opt_sep opt_list { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($6); $4->mark_braindead_shortcircuit (); $$ = parser.make_elseif_clause ($1, $4, $7, $2); } ; else_clause : ELSE stash_comment opt_sep opt_list { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); $$ = new octave::tree_if_clause ($4, $2); } ; // ================ // Switch statement // ================ switch_command : SWITCH stash_comment expression opt_sep case_list END { OCTAVE_YYUSE ($4); if (! ($$ = parser.finish_switch_command ($1, $3, $5, $6, $2))) { // finish_switch_command deleted $3 adn $5. YYABORT; } } ; case_list : // empty { $$ = 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 { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($6); $$ = parser.make_switch_case ($1, $4, $7, $2); } ; default_case : OTHERWISE stash_comment opt_sep opt_list { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); $$ = new octave::tree_switch_case ($4, $2); } ; // ======= // Looping // ======= loop_command : WHILE stash_comment expression stmt_begin opt_sep opt_list END { OCTAVE_YYUSE ($5); $3->mark_braindead_shortcircuit (); if (! ($$ = parser.make_while_command ($1, $3, $6, $7, $2))) { // make_while_command deleted $3 and $6. YYABORT; } } | DO stash_comment opt_sep opt_list UNTIL expression { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); $$ = parser.make_do_until_command ($5, $4, $6, $2); } | FOR stash_comment assign_lhs '=' expression stmt_begin opt_sep opt_list END { OCTAVE_YYUSE ($4); OCTAVE_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 { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); OCTAVE_YYUSE ($7); OCTAVE_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 { OCTAVE_YYUSE ($4); OCTAVE_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 { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); OCTAVE_YYUSE ($7); OCTAVE_YYUSE ($9); OCTAVE_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 { if (! ($$ = parser.make_continue_command ($1))) YYABORT; } | FUNC_RET { $$ = parser.make_return_command ($1); } ; // ======================= // Parallel execution pool // ======================= spmd_command : SPMD stash_comment opt_sep opt_list END { OCTAVE_YYUSE ($3); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_spmd_command ($1, $4, $5, lc, tc))) { // make_spmd_command deleted $4, LC, and TC. YYABORT; } } ; // ========== // Exceptions // ========== except_command : UNWIND stash_comment opt_sep opt_list CLEANUP stash_comment opt_sep opt_list END { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); OCTAVE_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 { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); OCTAVE_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 { OCTAVE_YYUSE ($3); if (! ($$ = parser.make_try_command ($1, $4, 0, nullptr, $5, $2, nullptr))) { // make_try_command deleted $4. YYABORT; } } ; // =========================================== // Some 'subroutines' for function definitions // =========================================== push_fcn_symtab : // empty { if (! parser.push_fcn_symtab ()) YYABORT; $$ = 0; } ; // =========================== // List of function parameters // =========================== param_list_beg : '(' { OCTAVE_YYUSE ($1); $$ = 0; lexer.m_looking_at_parameter_list = true; lexer.m_arguments_is_keyword = false; if (lexer.m_looking_at_function_handle) { // Will get a real name later. lexer.m_symtab_context.push (octave::symbol_scope ("parser:param_list_beg")); lexer.m_looking_at_function_handle--; lexer.m_looking_at_anon_fcn_args = true; } } ; param_list_end : ')' { OCTAVE_YYUSE ($1); $$ = 0; lexer.m_looking_at_parameter_list = false; lexer.m_arguments_is_keyword = true; lexer.m_looking_for_object_index = false; } ; opt_param_list : // empty { $$ = nullptr; } | param_list { $$ = $1; } ; param_list : param_list_beg param_list1 param_list_end { if ($2) lexer.mark_as_variables ($2->variable_names ()); $$ = $2; } | param_list_beg error { $$ = nullptr; parser.bison_error ("invalid parameter list"); YYABORT; } ; param_list1 : // empty { $$ = new octave::tree_parameter_list (octave::tree_parameter_list::in); } | param_list2 { $1->mark_as_formal_parameters (); if (parser.validate_param_list ($1, octave::tree_parameter_list::in)) { lexer.mark_as_variables ($1->variable_names ()); $$ = $1; } else { delete $1; YYABORT; } } ; param_list2 : param_list_elt { $$ = new octave::tree_parameter_list (octave::tree_parameter_list::in, $1); } | param_list2 ',' param_list_elt { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; param_list_elt : decl_elt { $$ = $1; } | magic_tilde { $$ = new octave::tree_decl_elt ($1); } ; // =================================== // List of function return value names // =================================== return_list : '[' ']' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($2); lexer.m_looking_at_return_list = false; $$ = new octave::tree_parameter_list (octave::tree_parameter_list::out); } | identifier { lexer.m_looking_at_return_list = false; octave::tree_parameter_list *tmp = new octave::tree_parameter_list (octave::tree_parameter_list::out, $1); // Even though this parameter list can contain only // a single identifier, we still need to validate it // to check for varargin or varargout. if (parser.validate_param_list (tmp, octave::tree_parameter_list::out)) $$ = tmp; else { delete tmp; YYABORT; } } | '[' return_list1 ']' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); lexer.m_looking_at_return_list = false; // Check for duplicate parameter names, varargin, // or varargout. if (parser.validate_param_list ($2, octave::tree_parameter_list::out)) $$ = $2; else { delete $2; YYABORT; } } ; return_list1 : identifier { $$ = new octave::tree_parameter_list (octave::tree_parameter_list::out, new octave::tree_decl_elt ($1)); } | return_list1 ',' identifier { OCTAVE_YYUSE ($2); $1->append (new octave::tree_decl_elt ($3)); $$ = $1; } ; // ======================= // Script or function file // ======================= parsing_local_fcns : // empty { parser.parsing_local_functions (true); } ; push_script_symtab : // empty { $$ = 0; // This scope may serve as the parent scope for local // functions in classdef files.. lexer.m_symtab_context.push (octave::symbol_scope ("parser:push_script_symtab")); } ; begin_file : push_script_symtab INPUT_FILE { $$ = 0; } ; file : begin_file opt_nl opt_list END_OF_INPUT { OCTAVE_YYUSE ($2); if (lexer.m_reading_fcn_file) { // Delete the dummy statement_list we created // after parsing the function. Any function // definitions found in the file have already // been stored in the symbol table or in // base_parser::m_primary_fcn. // Unused symbol table context. lexer.m_symtab_context.pop (); delete $3; if (! parser.validate_primary_fcn ()) YYABORT; } else { octave::tree_statement *end_of_script = parser.make_end ("endscript", true, $4->beg_pos (), $4->end_pos ()); parser.make_script ($3, end_of_script); if (! parser.validate_primary_fcn ()) YYABORT; } $$ = nullptr; } | begin_file opt_nl classdef parsing_local_fcns opt_sep opt_fcn_list END_OF_INPUT { OCTAVE_YYUSE ($2); OCTAVE_YYUSE ($5); OCTAVE_YYUSE ($7); // Unused symbol table context. lexer.m_symtab_context.pop (); if (! parser.finish_classdef_file ($3, $6)) YYABORT; $$ = nullptr; } ; // =================== // Function definition // =================== function_beg : push_fcn_symtab FCN { $$ = $2; if (lexer.m_reading_classdef_file || lexer.m_parsing_classdef) lexer.m_maybe_classdef_get_set_method = true; } ; fcn_name : identifier { if (! ($$ = parser.make_fcn_name ($1))) { // make_fcn_name deleted $1. YYABORT; } lexer.m_arguments_is_keyword = true; } | GET '.' identifier { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($2); $$ = $3; lexer.m_parsed_function_name.top () = true; lexer.m_maybe_classdef_get_set_method = false; lexer.m_parsing_classdef_get_method = true; lexer.m_arguments_is_keyword = true; } | SET '.' identifier { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($2); $$ = $3; lexer.m_parsed_function_name.top () = true; lexer.m_maybe_classdef_get_set_method = false; lexer.m_parsing_classdef_set_method = true; lexer.m_arguments_is_keyword = true; } ; function_end : END { parser.endfunction_found (true); if (parser.end_token_ok ($1, octave::token::function_end)) $$ = parser.make_end ("endfunction", false, $1->beg_pos (), $1->end_pos ()); else { parser.end_token_error ($1, octave::token::function_end); YYABORT; } } | END_OF_INPUT { // A lot of tests are based on the assumption that this is OK // if (lexer.m_reading_script_file) // { // parser.bison_error ("function body open at end of script"); // YYABORT; // } if (parser.endfunction_found ()) { parser.bison_error ("inconsistent function endings -- " "if one function is explicitly ended, " "so must all the others"); YYABORT; } if (! (lexer.m_reading_fcn_file || lexer.m_reading_script_file || lexer.input_from_eval_string ())) { parser.bison_error ("function body open at end of input"); YYABORT; } if (lexer.m_reading_classdef_file) { parser.bison_error ("classdef body open at end of input"); YYABORT; } $$ = parser.make_end ("endfunction", true, $1->beg_pos (), $1->end_pos ()); } ; function : function_beg stash_comment fcn_name opt_param_list opt_sep function_body function_end { OCTAVE_YYUSE ($5); $$ = parser.make_function ($1, nullptr, $3, $4, $6, $7, $2); } | function_beg stash_comment return_list '=' fcn_name opt_param_list opt_sep function_body function_end { OCTAVE_YYUSE ($4); OCTAVE_YYUSE ($7); $$ = parser.make_function ($1, $3, $5, $6, $8, $9, $2); } ; function_body : at_first_executable_stmt opt_list { OCTAVE_YYUSE ($1); $$ = $2; } | function_body1 opt_sep at_first_executable_stmt opt_list { OCTAVE_YYUSE ($2); if ($4) { for (const auto& elt : *($4)) $1->append (elt); } $4->clear (); delete ($4); $$ = $1; } ; at_first_executable_stmt : // empty { $$ = 0; lexer.m_arguments_is_keyword = false; } ; function_body1 : arguments_block { octave::tree_statement *stmt = parser.make_statement ($1); $$ = parser.make_statement_list (stmt); } | function_body1 opt_sep arguments_block { octave::tree_statement *stmt = parser.make_statement ($3); $$ = parser.append_statement_list ($1, $2, stmt, false); } ; arguments_block : arguments_beg stash_comment opt_sep args_attr_list args_validation_list opt_sep END { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($6); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_arguments_block ($1, $4, $5, $7, lc, tc))) { // make_arguments_block deleted $4, $5, LC, and TC. YYABORT; } lexer.m_arguments_is_keyword = true; } ; arguments_beg : ARGUMENTS { $$ = $1; lexer.m_arguments_is_keyword = false; } ; args_attr_list : // empty { $$ = nullptr; } | '(' identifier ')' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); // Error if $$ is nullptr. if (! ($$ = parser.make_args_attribute_list ($2))) { // make_args_attribute_list deleted $2. YYABORT; } } ; args_validation_list : arg_name arg_validation { $2->arg_name ($1); $$ = parser.make_args_validation_list ($2); } | args_validation_list sep arg_name arg_validation { OCTAVE_YYUSE ($2); $4->arg_name ($3); $$ = parser.append_args_validation_list ($1, $4); } ; // FIXME: Change grammar to allow IDENTIFIER to be be either // "NAME" or "NAME '.' NAME", possibly not entered in the symbol // table for the current scope. Also stash comments before identifier. arg_name : identifier { $$ = $1; } ; arg_validation : size_spec class_name validation_fcns default_value { if (! ($$ = parser.make_arg_validation ($1, $2, $3, $4))) { // make_arg_validation deleted ... YYABORT; } } ; size_spec : // empty { $$ = nullptr; } | '(' arg_list ')' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); if (! ($$ = parser.make_arg_size_spec ($2))) { // make_arg_size_spec deleted $2. YYABORT; } } ; class_name : // empty { $$ = nullptr; } | identifier { $$ = $1; } ; // Use argument list so we can accept anonymous functions. validation_fcns : // empty { $$ = nullptr; } | '{' arg_list '}' { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); if (! ($$ = parser.make_arg_validation_fcns ($2))) { // make_arg_validation_fcns deleted $2. YYABORT; } } ; default_value : // empty { $$ = nullptr; } | '=' expression { OCTAVE_YYUSE ($1); $$ = $2; } ; // ======== // Classdef // ======== classdef_beg : CLASSDEF { if (! lexer.m_reading_classdef_file) { parser.bison_error ("classdef must appear inside a file containing only a class definition"); YYABORT; } // Create invalid parent scope. lexer.m_symtab_context.push (octave::symbol_scope ()); lexer.m_parsing_classdef = true; lexer.m_parsing_classdef_decl = true; lexer.m_classdef_element_names_are_keywords = true; $$ = $1; } ; classdef : classdef_beg stash_comment attr_list identifier opt_sep superclass_list class_body END { OCTAVE_YYUSE ($5); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); lexer.m_parsing_classdef = false; if (! ($$ = parser.make_classdef ($1, $3, $4, $6, $7, $8, lc, tc))) { // make_classdef deleted $3, $4, $6, $7, LC, and // TC. YYABORT; } } ; attr_list : // empty { $$ = nullptr; } | '(' attr_list1 ')' opt_sep { OCTAVE_YYUSE ($1); OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($4); $$ = $2; } ; attr_list1 : attr { $$ = new octave::tree_classdef_attribute_list ($1); } | attr_list1 ',' attr { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; attr : identifier { $$ = new octave::tree_classdef_attribute ($1); } | identifier '=' expression { OCTAVE_YYUSE ($2); $$ = new octave::tree_classdef_attribute ($1, $3); } | EXPR_NOT identifier { OCTAVE_YYUSE ($1); $$ = new octave::tree_classdef_attribute ($2, false); } ; superclass_list : // empty { lexer.m_parsing_classdef_decl = false; lexer.m_parsing_classdef_superclass = false; $$ = nullptr; } | superclass_list1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_parsing_classdef_decl = false; lexer.m_parsing_classdef_superclass = false; $$ = $1; } ; superclass_list1 : EXPR_LT superclass { OCTAVE_YYUSE ($1); $$ = new octave::tree_classdef_superclass_list ($2); } | superclass_list1 EXPR_AND superclass { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; superclass : FQ_IDENT { $$ = new octave::tree_classdef_superclass ($1->text ()); } ; class_body : // empty { lexer.m_classdef_element_names_are_keywords = false; $$ = nullptr; } | class_body1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_classdef_element_names_are_keywords = false; $$ = $1; } ; class_body1 : properties_block { $$ = 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_body1 opt_sep properties_block { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } | class_body1 opt_sep methods_block { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } | class_body1 opt_sep events_block { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } | class_body1 opt_sep enum_block { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; properties_block : properties_beg stash_comment opt_sep attr_list property_list END { OCTAVE_YYUSE ($3); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_classdef_properties_block ($1, $4, $5, $6, lc, tc))) { // make_classdef_properties_block deleted $4, // $5, LC, and TC. YYABORT; } } ; properties_beg : PROPERTIES { lexer.m_classdef_element_names_are_keywords = false; $$ = $1; } ; property_list : // empty { lexer.m_classdef_element_names_are_keywords = true; $$ = nullptr; } | property_list1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_classdef_element_names_are_keywords = true; $$ = $1; } ; property_list1 : class_property { $$ = new octave::tree_classdef_property_list ($1); } | property_list1 sep class_property { OCTAVE_YYUSE ($2); // We don't look ahead to grab end-of-line comments. // Instead, they are grabbed when we see the // identifier that becomes the next element in the // list. If the element at the end of the list // doesn't have a doc string, see whether the // element we are adding is stroing an end-of-line // comment for us to use. octave::tree_classdef_property *last_elt = $1->back (); if (! last_elt->have_doc_string ()) { octave::comment_list *cl = $3->comments (); if (cl) { octave::comment_elt elt = cl->front (); if (elt.is_end_of_line ()) last_elt->doc_string (elt.text ()); } } $1->append ($3); $$ = $1; } ; class_property : stash_comment identifier arg_validation { // FIXME: Maybe this should be in a separate // base_parser::make_classdef_property function? octave::tree_arg_validation *av = $3; av->arg_name ($2); if (av->size_spec () || av->class_name () || av->validation_fcns ()) warning ("size, class, and validation function specifications are not yet supported for classdef properties; INCORRECT RESULTS ARE POSSIBLE!"); $$ = new octave::tree_classdef_property (av, $1); } ; methods_block : methods_beg stash_comment opt_sep attr_list methods_list END { OCTAVE_YYUSE ($3); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_classdef_methods_block ($1, $4, $5, $6, lc, tc))) { // make_classdef_methods_block deleted $4, $5, // LC, and TC. YYABORT; } } ; methods_beg : METHODS { lexer.m_classdef_element_names_are_keywords = false; $$ = $1; } ; method_decl1 : identifier { if (! ($$ = parser.start_classdef_external_method ($1, nullptr))) YYABORT; } | identifier param_list { if (! ($$ = parser.start_classdef_external_method ($1, $2))) YYABORT; } ; method_decl : stash_comment method_decl1 { $$ = parser.finish_classdef_external_method ($2, nullptr, $1); } | stash_comment return_list '=' { OCTAVE_YYUSE ($3); lexer.m_defining_func++; lexer.m_parsed_function_name.push (false); } method_decl1 { lexer.m_defining_func--; lexer.m_parsed_function_name.pop (); $$ = parser.finish_classdef_external_method ($5, $2, $1); } ; method : method_decl { $$ = $1; } | function { $$ = $1; } ; methods_list : // empty { lexer.m_classdef_element_names_are_keywords = true; $$ = nullptr; } | methods_list1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_classdef_element_names_are_keywords = true; $$ = $1; } ; methods_list1 : method { octave_value fcn; if ($1) fcn = $1->function (); delete $1; $$ = new octave::tree_classdef_methods_list (fcn); } | methods_list1 opt_sep method { OCTAVE_YYUSE ($2); octave_value fcn; if ($3) fcn = $3->function (); delete $3; $1->append (fcn); $$ = $1; } ; events_block : events_beg stash_comment opt_sep attr_list events_list END { OCTAVE_YYUSE ($3); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_classdef_events_block ($1, $4, $5, $6, lc, tc))) { // make_classdef_events_block deleted $4, $5, // LC, and TC. YYABORT; } } ; events_beg : EVENTS { lexer.m_classdef_element_names_are_keywords = false; $$ = $1; } ; events_list : // empty { lexer.m_classdef_element_names_are_keywords = true; $$ = nullptr; } | events_list1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_classdef_element_names_are_keywords = true; $$ = $1; } ; events_list1 : class_event { $$ = new octave::tree_classdef_events_list ($1); } | events_list1 opt_sep class_event { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; class_event : stash_comment identifier { $$ = new octave::tree_classdef_event ($2, $1); } ; enum_block : enumeration_beg stash_comment opt_sep attr_list enum_list END { OCTAVE_YYUSE ($3); octave::comment_list *lc = $2; octave::comment_list *tc = lexer.get_comment (); if (! ($$ = parser.make_classdef_enum_block ($1, $4, $5, $6, lc, tc))) { // make_classdef_enum_block deleted $4, $5, LC, // and TC. YYABORT; } } ; enumeration_beg : ENUMERATION { lexer.m_classdef_element_names_are_keywords = false; $$ = $1; } ; enum_list : // empty { lexer.m_classdef_element_names_are_keywords = true; $$ = nullptr; } | enum_list1 opt_sep { OCTAVE_YYUSE ($2); lexer.m_classdef_element_names_are_keywords = true; $$ = $1; } ; enum_list1 : class_enum { $$ = new octave::tree_classdef_enum_list ($1); } | enum_list1 opt_sep class_enum { OCTAVE_YYUSE ($2); $1->append ($3); $$ = $1; } ; class_enum : stash_comment identifier '(' expression ')' { OCTAVE_YYUSE ($3); OCTAVE_YYUSE ($5); $$ = new octave::tree_classdef_enum ($2, $4, $1); } ; // ============= // Miscellaneous // ============= stmt_begin : // empty { $$ = 0; lexer.m_at_beginning_of_statement = true; } ; anon_fcn_begin : // empty { $$ = 0; lexer.m_at_beginning_of_statement = true; lexer.m_parsing_anon_fcn_body = true; } ; stash_comment : // empty { $$ = lexer.get_comment (); } ; parse_error : LEXICAL_ERROR { $$ = 0; std::string msg = $1->text (); parser.bison_error (msg.c_str ()); } | error { $$ = 0; } ; sep_no_nl : ',' { OCTAVE_YYUSE ($1); $$ = ','; } | ';' { OCTAVE_YYUSE ($1); $$ = ';'; } | sep_no_nl ',' { OCTAVE_YYUSE ($2); $$ = $1; } | sep_no_nl ';' { OCTAVE_YYUSE ($2); $$ = $1; } ; opt_sep_no_nl : // empty { $$ = 0; } | sep_no_nl { $$ = $1; } ; opt_nl : // empty { $$ = 0; } | nl { $$ = $1; } ; nl : '\n' { OCTAVE_YYUSE ($1); $$ = '\n'; } | nl '\n' { OCTAVE_YYUSE ($2); $$ = $1; } ; sep : ',' { OCTAVE_YYUSE ($1); $$ = ','; } | ';' { OCTAVE_YYUSE ($1); $$ = ';'; } | '\n' { OCTAVE_YYUSE ($1); $$ = '\n'; } | sep ',' { OCTAVE_YYUSE ($2); $$ = $1; } | sep ';' { OCTAVE_YYUSE ($2); $$ = $1; } | sep '\n' { OCTAVE_YYUSE ($2); $$ = $1; } ; opt_sep : // empty { $$ = 0; } | sep { $$ = $1; } ; %% #if defined (HAVE_PRAGMA_GCC_DIAGNOSTIC) // Restore prevailing warning state for remainder of the file. # pragma GCC diagnostic pop #endif // Generic error messages. #undef lexer #undef scanner static void yyerror (octave::base_parser& parser, const char *s) { parser.bison_error (s); } OCTAVE_NAMESPACE_BEGIN class parse_exception : public std::runtime_error { public: parse_exception (const std::string& message, const std::string& fcn_name = "", const std::string& file_name = "", int line = -1, int column = -1) : runtime_error (message), m_message (message), m_fcn_name (fcn_name), m_file_name (file_name), m_line (line), m_column (column) { } parse_exception (const parse_exception&) = default; parse_exception& operator = (const parse_exception&) = default; ~parse_exception (void) = default; std::string message (void) const { return m_message; } // Provided for std::exception interface. const char * what (void) const noexcept { return m_message.c_str (); } std::string fcn_name (void) const { return m_fcn_name; } std::string file_name (void) const { return m_file_name; } int line (void) const { return m_line; } int column (void) const { return m_column; } // virtual void display (std::ostream& os) const; private: std::string m_message; std::string m_fcn_name; std::string m_file_name; int m_line; int m_column; }; class parse_tree_validator : public tree_walker { public: parse_tree_validator (void) : m_scope (), m_error_list () { } parse_tree_validator (const parse_tree_validator&) = delete; parse_tree_validator& operator = (const parse_tree_validator&) = delete; ~parse_tree_validator (void) = default; symbol_scope get_scope (void) const { return m_scope; } bool ok (void) const { return m_error_list.empty (); } std::list<parse_exception> error_list (void) const { return m_error_list; } void visit_octave_user_script (octave_user_script& script) { unwind_protect_var<symbol_scope> restore_var (m_scope, script.scope ()); tree_statement_list *stmt_list = script.body (); if (stmt_list) stmt_list->accept (*this); } void visit_octave_user_function (octave_user_function& fcn) { unwind_protect_var<symbol_scope> restore_var (m_scope, fcn.scope ()); tree_statement_list *stmt_list = fcn.body (); if (stmt_list) stmt_list->accept (*this); std::map<std::string, octave_value> subfcns = fcn.subfunctions (); if (! subfcns.empty ()) { for (auto& nm_val : subfcns) { octave_user_function *subfcn = nm_val.second.user_function_value (); if (subfcn) subfcn->accept (*this); } } } void visit_index_expression (tree_index_expression& idx_expr) { if (idx_expr.is_word_list_cmd ()) { std::string sym_nm = idx_expr.name (); if (m_scope.is_variable (sym_nm)) { std::string message = sym_nm + ": invalid use of symbol as both variable and command"; parse_exception pe (message, m_scope.fcn_name (), m_scope.fcn_file_name (), idx_expr.line (), idx_expr.column ()); m_error_list.push_back (pe); } } } private: symbol_scope m_scope; std::list<parse_exception> m_error_list; }; std::size_t base_parser::parent_scope_info::size (void) const { return m_info.size (); } void base_parser::parent_scope_info::push (const value_type& elt) { m_info.push_back (elt); } void base_parser::parent_scope_info::push (const symbol_scope& scope) { push (value_type (scope, "")); } void base_parser::parent_scope_info::pop (void) { m_info.pop_back (); } bool base_parser::parent_scope_info::name_ok (const std::string& name) { // Name can't be the same as any parent function or any other // function we've already seen. We could maintain a complex // tree structure of names, or we can just store the set of // full names of all the functions, which must be unique. std::string full_name; for (std::size_t i = 0; i < size()-1; i++) { const value_type& elt = m_info[i]; if (name == elt.second) return false; full_name += elt.second + ">"; } full_name += name; if (m_all_names.find (full_name) != m_all_names.end ()) { // Return false (failure) if we are parsing a subfunction, local // function, or nested function. Otherwise, it is OK to have a // duplicate name. return ! (m_parser.parsing_subfunctions () || m_parser.parsing_local_functions () || m_parser.curr_fcn_depth () > 0); } m_all_names.insert (full_name); return true; } bool base_parser::parent_scope_info::name_current_scope (const std::string& name) { if (! name_ok (name)) return false; if (size () > 0) m_info.back().second = name; return true; } symbol_scope base_parser::parent_scope_info::parent_scope (void) const { return size () > 1 ? m_info[size()-2].first : symbol_scope (); } std::string base_parser::parent_scope_info::parent_name (void) const { return m_info[size()-2].second; } void base_parser::parent_scope_info::clear (void) { m_info.clear (); m_all_names.clear (); } base_parser::base_parser (base_lexer& lxr) : m_endfunction_found (false), m_autoloading (false), m_fcn_file_from_relative_lookup (false), m_parsing_subfunctions (false), m_parsing_local_functions (false), m_max_fcn_depth (-1), m_curr_fcn_depth (-1), m_primary_fcn_scope (), m_curr_class_name (), m_curr_package_name (), m_function_scopes (*this), m_primary_fcn (), m_subfunction_names (), m_classdef_object (), m_stmt_list (), m_lexer (lxr), m_parser_state (yypstate_new ()) { } base_parser::~base_parser (void) { delete &m_lexer; // FIXME: Deleting the internal Bison parser state structure does // not clean up any partial parse trees in the event of an interrupt or // error. It's not clear how to safely do that with the C language // parser that Bison generates. The C++ language parser that Bison // generates would do it for us automatically whenever an exception // is thrown while parsing input, but there is currently no C++ // interface for a push parser. yypstate_delete (static_cast<yypstate *> (m_parser_state)); } void base_parser::reset (void) { m_endfunction_found = false; m_autoloading = false; m_fcn_file_from_relative_lookup = false; m_parsing_subfunctions = false; m_parsing_local_functions = false; m_max_fcn_depth = -1; m_curr_fcn_depth = -1; m_primary_fcn_scope = symbol_scope (); m_curr_class_name = ""; m_curr_package_name = ""; m_function_scopes.clear (); m_primary_fcn = octave_value (); m_subfunction_names.clear (); m_classdef_object.reset (); m_stmt_list.reset (); m_lexer.reset (); yypstate_delete (static_cast<yypstate *> (m_parser_state)); m_parser_state = yypstate_new (); } // Error messages for mismatched end tokens. static std::string end_token_as_string (token::end_tok_type ettype) { std::string retval = "<unknown>"; switch (ettype) { case token::simple_end: retval = "end"; break; case token::classdef_end: retval = "endclassdef"; break; case token::enumeration_end: retval = "endenumeration"; break; case token::events_end: retval = "endevents"; break; case token::for_end: retval = "endfor"; break; case token::function_end: retval = "endfunction"; break; case token::if_end: retval = "endif"; break; case token::methods_end: retval = "endmethods"; break; case token::parfor_end: retval = "endparfor"; break; case token::properties_end: retval = "endproperties"; break; case token::spmd_end: retval = "endspmd"; break; case token::switch_end: retval = "endswitch"; break; case token::try_catch_end: retval = "end_try_catch"; break; case token::unwind_protect_end: retval = "end_unwind_protect"; break; case token::while_end: retval = "endwhile"; break; default: panic_impossible (); break; } return retval; } void base_parser::statement_list (std::shared_ptr<tree_statement_list>& lst) { if (! lst) return; if (m_stmt_list) { // Append additional code to existing statement list. while (! lst->empty ()) { m_stmt_list->push_back (lst->front ()); lst->pop_front (); } } else m_stmt_list = lst; } void base_parser::end_token_error (token *tok, token::end_tok_type expected) { std::string msg = ("'" + end_token_as_string (expected) + "' command matched by '" + end_token_as_string (tok->ettype ()) + "'"); bison_error (msg, tok->beg_pos ()); } // Check to see that end tokens are properly matched. bool base_parser::end_token_ok (token *tok, token::end_tok_type expected) { token::end_tok_type ettype = tok->ettype (); return ettype == expected || ettype == token::simple_end; } bool base_parser::push_fcn_symtab (void) { m_curr_fcn_depth++; if (m_max_fcn_depth < m_curr_fcn_depth) m_max_fcn_depth = m_curr_fcn_depth; // Will get a real name later. m_lexer.m_symtab_context.push (symbol_scope ("parser:push_fcn_symtab")); m_function_scopes.push (m_lexer.m_symtab_context.curr_scope ()); if (! m_lexer.m_reading_script_file && m_curr_fcn_depth == 0 && ! m_parsing_subfunctions) { m_primary_fcn_scope = m_lexer.m_symtab_context.curr_scope (); m_primary_fcn_scope.mark_primary_fcn_scope (); } if (m_lexer.m_reading_script_file && m_curr_fcn_depth > 0) { bison_error ("nested functions not implemented in this context"); return false; } return true; } // Make a constant. tree_constant * base_parser::make_constant (int op, token *tok_val) { int l = tok_val->line (); int c = tok_val->column (); tree_constant *retval = nullptr; switch (op) { case NUMBER: { retval = new tree_constant (tok_val->number (), l, c); retval->stash_original_text (tok_val->text_rep ()); } break; case DQ_STRING: case SQ_STRING: { std::string txt = tok_val->text (); char delim = op == DQ_STRING ? '"' : '\''; octave_value tmp (txt, delim); if (txt.empty ()) { if (op == DQ_STRING) tmp = octave_null_str::instance; else tmp = octave_null_sq_str::instance; } retval = new tree_constant (tmp, l, c); if (op == DQ_STRING) txt = undo_string_escapes (txt); // FIXME: maybe this should also be handled by // tok_val->text_rep () for character strings? retval->stash_original_text (delim + txt + delim); } break; default: panic_impossible (); break; } return retval; } // Make a function handle. tree_fcn_handle * base_parser::make_fcn_handle (token *tok_val) { int l = tok_val->line (); int c = tok_val->column (); tree_fcn_handle *retval = new tree_fcn_handle (tok_val->text (), l, c); return retval; } // Make an anonymous function handle. tree_anon_fcn_handle * base_parser::make_anon_fcn_handle (tree_parameter_list *param_list, tree_expression *expr, const filepos& at_pos) { // FIXME: We need to examine EXPR and issue an error if any // sub-expression contains an assignment, compound assignment, // increment, or decrement operator. anon_fcn_validator validator (param_list, expr); if (! validator.ok ()) { delete param_list; delete expr; bison_error (validator.message (), validator.line (), validator.column ()); return nullptr; } symbol_scope fcn_scope = m_lexer.m_symtab_context.curr_scope (); symbol_scope parent_scope = m_lexer.m_symtab_context.parent_scope (); m_lexer.m_symtab_context.pop (); expr->set_print_flag (false); fcn_scope.mark_static (); int at_line = at_pos.line (); int at_column = at_pos.column (); tree_anon_fcn_handle *retval = new tree_anon_fcn_handle (param_list, expr, fcn_scope, parent_scope, at_line, at_column); std::ostringstream buf; tree_print_code tpc (buf); retval->accept (tpc); std::string file = m_lexer.m_fcn_file_full_name; if (! file.empty ()) buf << ": file: " << file; else if (m_lexer.input_from_terminal ()) buf << ": *terminal input*"; else if (m_lexer.input_from_eval_string ()) buf << ": *eval string*"; buf << ": line: " << at_line << " column: " << at_column; std::string scope_name = buf.str (); fcn_scope.cache_name (scope_name); // FIXME: Stash the filename. This does not work and produces // errors when executed. //retval->stash_file_name (m_lexer.m_fcn_file_name); return retval; } // Build a colon expression. tree_expression * base_parser::make_colon_expression (tree_expression *base, tree_expression *limit, tree_expression *incr) { tree_expression *retval = nullptr; if (! base || ! limit) { delete base; delete limit; delete incr; return retval; } int l = base->line (); int c = base->column (); tree_colon_expression *expr = new tree_colon_expression (base, limit, incr, l, c); retval = expr; if (base->is_constant () && limit->is_constant () && (! incr || incr->is_constant ())) { interpreter& interp = __get_interpreter__ ("finish_colon_expression"); try { // If the evaluation generates a warning message, restore // the previous value of last_warning_message and skip the // conversion to a constant value. error_system& es = interp.get_error_system (); unwind_action restore_last_warning_message (&error_system::set_last_warning_message, &es, es.last_warning_message ("")); unwind_action restore_discard_warning_messages (&error_system::set_discard_warning_messages, &es, es.discard_warning_messages (true)); tree_evaluator& tw = interp.get_evaluator (); octave_value tmp = expr->evaluate (tw); std::string msg = es.last_warning_message (); if (msg.empty ()) { tree_constant *tc_retval = new tree_constant (tmp, expr->line (), expr->column ()); std::ostringstream buf; tree_print_code tpc (buf); expr->accept (tpc); tc_retval->stash_original_text (buf.str ()); delete expr; retval = tc_retval; } } catch (const execution_exception&) { interp.recover_from_exception (); } } return retval; } // Build a binary expression. tree_expression * base_parser::make_binary_op (int op, tree_expression *op1, token *tok_val, tree_expression *op2) { octave_value::binary_op t = octave_value::unknown_binary_op; switch (op) { case POW: t = octave_value::op_pow; break; case EPOW: t = octave_value::op_el_pow; break; case '+': t = octave_value::op_add; break; case '-': t = octave_value::op_sub; break; case '*': t = octave_value::op_mul; break; case '/': t = octave_value::op_div; break; case EMUL: t = octave_value::op_el_mul; break; case EDIV: t = octave_value::op_el_div; break; case LEFTDIV: t = octave_value::op_ldiv; break; case ELEFTDIV: t = octave_value::op_el_ldiv; break; case EXPR_LT: t = octave_value::op_lt; break; case EXPR_LE: t = octave_value::op_le; break; case EXPR_EQ: t = octave_value::op_eq; break; case EXPR_GE: t = octave_value::op_ge; break; case EXPR_GT: t = octave_value::op_gt; break; case EXPR_NE: t = octave_value::op_ne; break; case EXPR_AND: t = octave_value::op_el_and; break; case EXPR_OR: t = octave_value::op_el_or; break; default: panic_impossible (); break; } int l = tok_val->line (); int c = tok_val->column (); return maybe_compound_binary_expression (op1, op2, l, c, t); } // Build a boolean expression. tree_expression * base_parser::make_boolean_op (int op, tree_expression *op1, token *tok_val, tree_expression *op2) { tree_boolean_expression::type t; switch (op) { case EXPR_AND_AND: t = tree_boolean_expression::bool_and; break; case EXPR_OR_OR: t = tree_boolean_expression::bool_or; break; default: panic_impossible (); break; } int l = tok_val->line (); int c = tok_val->column (); return new tree_boolean_expression (op1, op2, l, c, t); } // Build a prefix expression. tree_expression * base_parser::make_prefix_op (int op, tree_expression *op1, token *tok_val) { octave_value::unary_op t = octave_value::unknown_unary_op; switch (op) { case EXPR_NOT: t = octave_value::op_not; break; case '+': t = octave_value::op_uplus; break; case '-': t = octave_value::op_uminus; break; case PLUS_PLUS: t = octave_value::op_incr; break; case MINUS_MINUS: t = octave_value::op_decr; break; default: panic_impossible (); break; } int l = tok_val->line (); int c = tok_val->column (); return new tree_prefix_expression (op1, l, c, t); } // Build a postfix expression. tree_expression * base_parser::make_postfix_op (int op, tree_expression *op1, token *tok_val) { octave_value::unary_op t = octave_value::unknown_unary_op; switch (op) { case HERMITIAN: t = octave_value::op_hermitian; break; case TRANSPOSE: t = octave_value::op_transpose; break; case PLUS_PLUS: t = octave_value::op_incr; break; case MINUS_MINUS: t = octave_value::op_decr; break; default: panic_impossible (); break; } int l = tok_val->line (); int c = tok_val->column (); return new tree_postfix_expression (op1, l, c, t); } // Build an unwind-protect command. tree_command * base_parser::make_unwind_command (token *unwind_tok, tree_statement_list *body, tree_statement_list *cleanup_stmts, token *end_tok, comment_list *lc, comment_list *mc) { tree_command *retval = nullptr; if (end_token_ok (end_tok, token::unwind_protect_end)) { comment_list *tc = m_lexer.m_comment_buf.get_comment (); int l = unwind_tok->line (); int c = unwind_tok->column (); retval = new tree_unwind_protect_command (body, cleanup_stmts, lc, mc, tc, l, c); } else { delete body; delete cleanup_stmts; end_token_error (end_tok, token::unwind_protect_end); } return retval; } // Build a try-catch command. tree_command * base_parser::make_try_command (token *try_tok, tree_statement_list *body, char catch_sep, tree_statement_list *cleanup_stmts, token *end_tok, comment_list *lc, comment_list *mc) { tree_command *retval = nullptr; if (end_token_ok (end_tok, token::try_catch_end)) { comment_list *tc = m_lexer.m_comment_buf.get_comment (); int l = try_tok->line (); int c = try_tok->column (); tree_identifier *id = nullptr; if (! catch_sep && cleanup_stmts && ! cleanup_stmts->empty ()) { tree_statement *stmt = cleanup_stmts->front (); if (stmt) { tree_expression *expr = stmt->expression (); if (expr && expr->is_identifier ()) { id = dynamic_cast<tree_identifier *> (expr); cleanup_stmts->pop_front (); stmt->set_expression (nullptr); delete stmt; } } } retval = new tree_try_catch_command (body, cleanup_stmts, id, lc, mc, tc, l, c); } else { delete body; delete cleanup_stmts; end_token_error (end_tok, token::try_catch_end); } return retval; } // Build a while command. tree_command * base_parser::make_while_command (token *while_tok, tree_expression *expr, tree_statement_list *body, token *end_tok, comment_list *lc) { tree_command *retval = nullptr; maybe_warn_assign_as_truth_value (expr); if (end_token_ok (end_tok, token::while_end)) { comment_list *tc = m_lexer.m_comment_buf.get_comment (); m_lexer.m_looping--; int l = while_tok->line (); int c = while_tok->column (); retval = new tree_while_command (expr, body, lc, tc, l, c); } else { delete expr; delete body; end_token_error (end_tok, token::while_end); } return retval; } // Build a do-until command. tree_command * base_parser::make_do_until_command (token *until_tok, tree_statement_list *body, tree_expression *expr, comment_list *lc) { maybe_warn_assign_as_truth_value (expr); comment_list *tc = m_lexer.m_comment_buf.get_comment (); m_lexer.m_looping--; int l = until_tok->line (); int c = until_tok->column (); return new tree_do_until_command (expr, body, lc, tc, l, c); } // Build a for command. tree_command * base_parser::make_for_command (int tok_id, token *for_tok, tree_argument_list *lhs, tree_expression *expr, tree_expression *maxproc, tree_statement_list *body, token *end_tok, comment_list *lc) { tree_command *retval = nullptr; bool parfor = tok_id == PARFOR; if (end_token_ok (end_tok, parfor ? token::parfor_end : token::for_end)) { expr->mark_as_for_cmd_expr (); comment_list *tc = m_lexer.m_comment_buf.get_comment (); m_lexer.m_looping--; int l = for_tok->line (); int c = for_tok->column (); if (lhs->length () == 1) { tree_expression *tmp = lhs->remove_front (); m_lexer.mark_as_variable (tmp->name ()); retval = new tree_simple_for_command (parfor, tmp, expr, maxproc, body, lc, tc, l, c); delete lhs; } else { if (parfor) { delete lhs; delete expr; delete maxproc; delete body; bison_error ("invalid syntax for parfor statement"); } m_lexer.mark_as_variables (lhs->variable_names ()); retval = new tree_complex_for_command (lhs, expr, body, lc, tc, l, c); } } else { delete lhs; delete expr; delete maxproc; delete body; end_token_error (end_tok, parfor ? token::parfor_end : token::for_end); } return retval; } // Build a break command. tree_command * base_parser::make_break_command (token *break_tok) { int l = break_tok->line (); int c = break_tok->column (); if (! m_lexer.m_looping) { bison_error ("break must appear within a loop"); return nullptr; } else return new tree_break_command (l, c); } // Build a continue command. tree_command * base_parser::make_continue_command (token *continue_tok) { int l = continue_tok->line (); int c = continue_tok->column (); if (! m_lexer.m_looping) { bison_error ("continue must appear within a loop"); return nullptr; } else return new tree_continue_command (l, c); } // Build a return command. tree_command * base_parser::make_return_command (token *return_tok) { int l = return_tok->line (); int c = return_tok->column (); return new tree_return_command (l, c); } // Build an spmd command. tree_spmd_command * base_parser::make_spmd_command (token *spmd_tok, tree_statement_list *body, token *end_tok, comment_list *lc, comment_list *tc) { tree_spmd_command *retval = nullptr; if (end_token_ok (end_tok, token::spmd_end)) { int l = spmd_tok->line (); int c = spmd_tok->column (); retval = new tree_spmd_command (body, lc, tc, l, c); } else { delete body; delete lc; delete tc; end_token_error (end_tok, token::spmd_end); } return retval; } // Start an if command. tree_if_command_list * base_parser::start_if_command (tree_expression *expr, tree_statement_list *list) { maybe_warn_assign_as_truth_value (expr); 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", eq_tok->beg_pos ()); return nullptr; } if (lhs->is_simple_assign_lhs ()) { // We are looking at a simple assignment statement like x = rhs; tree_expression *tmp = lhs->remove_front (); if ((tmp->is_identifier () || tmp->is_index_expression ()) && iskeyword (tmp->name ())) { std::string kw = tmp->name (); delete tmp; delete lhs; delete rhs; bison_error ("invalid assignment to keyword \"" + kw + "\"", eq_tok->beg_pos ()); return nullptr; } delete lhs; m_lexer.mark_as_variable (tmp->name ()); return new tree_simple_assignment (tmp, rhs, false, l, c, t); } else { std::list<std::string> names = lhs->variable_names (); for (const auto& kw : names) { if (iskeyword (kw)) { delete lhs; delete rhs; bison_error ("invalid assignment to keyword \"" + kw + "\"", eq_tok->beg_pos ()); return nullptr; } } m_lexer.mark_as_variables (names); return new tree_multi_assignment (lhs, rhs, false, l, c); } } void base_parser::make_script (tree_statement_list *cmds, tree_statement *end_script) { if (! cmds) cmds = new tree_statement_list (); cmds->append (end_script); symbol_scope script_scope = m_lexer.m_symtab_context.curr_scope (); script_scope.cache_name (m_lexer.m_fcn_file_full_name); script_scope.cache_fcn_file_name (m_lexer.m_fcn_file_full_name); script_scope.cache_dir_name (m_lexer.m_dir_name); octave_user_script *script = new octave_user_script (m_lexer.m_fcn_file_full_name, m_lexer.m_fcn_file_name, script_scope, cmds, m_lexer.m_help_text); m_lexer.m_symtab_context.pop (); m_lexer.m_help_text = ""; sys::time now; script->stash_fcn_file_time (now); script->stash_dir_name (m_lexer.m_dir_name); m_primary_fcn = octave_value (script); } tree_identifier * base_parser::make_fcn_name (tree_identifier *id) { std::string id_name = id->name (); // Make classdef local functions unique from classdef methods. if (m_parsing_local_functions && m_curr_fcn_depth == 0) id_name = m_lexer.m_fcn_file_name + ">" + id_name; if (! m_function_scopes.name_current_scope (id_name)) { bison_error ("duplicate subfunction or nested function name", id->line (), id->column ()); delete id; return nullptr; } symbol_scope curr_scope = m_lexer.m_symtab_context.curr_scope (); curr_scope.cache_name (id_name); m_lexer.m_parsed_function_name.top () = true; m_lexer.m_maybe_classdef_get_set_method = false; return id; } // Define a function. // FIXME: combining start_function, finish_function, and // recover_from_parsing_function should be possible, but it makes // for a large mess. Maybe this could be a bit better organized? tree_function_def * base_parser::make_function (token *fcn_tok, tree_parameter_list *ret_list, tree_identifier *id, tree_parameter_list *param_list, tree_statement_list *body, tree_statement *end_fcn_stmt, comment_list *lc) { int l = fcn_tok->line (); int c = fcn_tok->column (); octave_user_function *tmp_fcn = start_function (id, param_list, body, end_fcn_stmt); tree_function_def *retval = finish_function (ret_list, tmp_fcn, lc, l, c); recover_from_parsing_function (); return retval; } // Begin defining a function. octave_user_function * base_parser::start_function (tree_identifier *id, tree_parameter_list *param_list, tree_statement_list *body, tree_statement *end_fcn_stmt) { // We'll fill in the return list later. std::string id_name = id->name (); delete id; if (m_lexer.m_parsing_classdef_get_method) id_name.insert (0, "get."); else if (m_lexer.m_parsing_classdef_set_method) id_name.insert (0, "set."); m_lexer.m_parsing_classdef_get_method = false; m_lexer.m_parsing_classdef_set_method = false; if (! body) body = new tree_statement_list (); body->append (end_fcn_stmt); octave_user_function *fcn = new octave_user_function (m_lexer.m_symtab_context.curr_scope (), param_list, nullptr, body); comment_list *tc = m_lexer.m_comment_buf.get_comment (); fcn->stash_trailing_comment (tc); fcn->stash_fcn_end_location (end_fcn_stmt->line (), end_fcn_stmt->column ()); // If input is coming from a file, issue a warning if the name of // the file does not match the name of the function stated in the // file. Matlab doesn't provide a diagnostic (it ignores the stated // name). if (! m_autoloading && m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0 && ! m_parsing_subfunctions) { // FIXME: should m_lexer.m_fcn_file_name already be // preprocessed when we get here? It seems to only be a // problem with relative filenames. std::string nm = m_lexer.m_fcn_file_name; std::size_t pos = nm.find_last_of (sys::file_ops::dir_sep_chars ()); if (pos != std::string::npos) nm = m_lexer.m_fcn_file_name.substr (pos+1); if (nm != id_name) { warning_with_id ("Octave:function-name-clash", "function name '%s' does not agree with function filename '%s'", id_name.c_str (), m_lexer.m_fcn_file_full_name.c_str ()); id_name = nm; } } sys::time now; fcn->stash_fcn_file_name (m_lexer.m_fcn_file_full_name); fcn->stash_fcn_file_time (now); fcn->stash_dir_name (m_lexer.m_dir_name); fcn->stash_package_name (m_lexer.m_package_name); fcn->mark_as_system_fcn_file (); fcn->stash_function_name (id_name); if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_classdef_file || m_autoloading) { if (m_fcn_file_from_relative_lookup) fcn->mark_relative (); if (m_lexer.m_parsing_class_method) { if (m_lexer.m_parsing_classdef) { if (m_curr_class_name == id_name) fcn->mark_as_classdef_constructor (); else fcn->mark_as_classdef_method (); } else { if (m_curr_class_name == id_name) fcn->mark_as_legacy_constructor (); else fcn->mark_as_legacy_method (); } fcn->stash_dispatch_class (m_curr_class_name); } std::string nm = fcn->fcn_file_name (); sys::file_stat fs (nm); if (fs && fs.is_newer (now)) warning_with_id ("Octave:future-time-stamp", "time stamp for '%s' is in the future", nm.c_str ()); } else if (! m_lexer.input_from_tmp_history_file () && ! m_lexer.m_force_script && m_lexer.m_reading_script_file && m_lexer.m_fcn_file_name == id_name) { warning ("function '%s' defined within script file '%s'", id_name.c_str (), m_lexer.m_fcn_file_full_name.c_str ()); } // Record help text for functions other than nested functions. // We cannot currently record help for nested functions (bug #46008) // because the doc_string of the outermost function is read first, // whereas this function is called for the innermost function first. // We could have a stack of help_text in lexer. if (! m_lexer.m_help_text.empty () && m_curr_fcn_depth == 0) { fcn->document (m_lexer.m_help_text); m_lexer.m_help_text = ""; } if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0 && ! m_parsing_subfunctions) m_primary_fcn = octave_value (fcn); return fcn; } tree_statement * base_parser::make_end (const std::string& type, bool eof, const filepos& beg_pos, const filepos& /*end_pos*/) { int l = beg_pos.line (); int c = beg_pos.column (); return make_statement (new tree_no_op_command (type, eof, l, c)); } tree_function_def * base_parser::finish_function (tree_parameter_list *ret_list, octave_user_function *fcn, comment_list *lc, int l, int c) { tree_function_def *retval = nullptr; if (! ret_list) ret_list = new tree_parameter_list (tree_parameter_list::out); ret_list->mark_as_formal_parameters (); if (fcn) { std::string fcn_nm = fcn->name (); std::string file = fcn->fcn_file_name (); std::string tmp = fcn_nm; if (! file.empty ()) tmp += ": " + file; symbol_scope fcn_scope = fcn->scope (); fcn_scope.cache_name (tmp); fcn_scope.cache_fcn_name (fcn_nm); fcn_scope.cache_fcn_file_name (file); fcn_scope.cache_dir_name (m_lexer.m_dir_name); if (lc) fcn->stash_leading_comment (lc); fcn->define_ret_list (ret_list); if (m_curr_fcn_depth > 0 || m_parsing_subfunctions) { fcn->stash_fcn_location (l, c); octave_value ov_fcn (fcn); if (m_endfunction_found && m_function_scopes.size () > 1) { fcn->mark_as_nested_function (); fcn_scope.set_nesting_depth (m_curr_fcn_depth); symbol_scope pscope = m_function_scopes.parent_scope (); fcn_scope.set_parent (pscope); fcn_scope.set_primary_parent (m_primary_fcn_scope); pscope.install_nestfunction (fcn_nm, ov_fcn, fcn_scope); // For nested functions, the list of parent functions is // set in symbol_scope::update_nest. } else { fcn->mark_as_subfunction (); m_subfunction_names.push_back (fcn_nm); fcn_scope.set_parent (m_primary_fcn_scope); if (m_parsing_subfunctions) fcn_scope.set_primary_parent (m_primary_fcn_scope); m_primary_fcn_scope.install_subfunction (fcn_nm, ov_fcn); } } if (m_curr_fcn_depth == 0) fcn_scope.update_nest (); if (! m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0) { // We are either reading a script file or defining a function // at the command line, so this definition creates a // tree_function object that is placed in the parse tree. // Otherwise, it is just inserted in the symbol table, // either as a subfunction or nested function (see above), // or as the primary function for the file, via // m_primary_fcn (see also load_fcn_from_file,, // parse_fcn_file, and // fcn_info::fcn_info_rep::find_user_function). if (m_lexer.m_buffer_function_text) { fcn->cache_function_text (m_lexer.m_function_text, fcn->time_parsed ()); m_lexer.m_buffer_function_text = false; } retval = new tree_function_def (fcn); } } return retval; } tree_arguments_block * base_parser::make_arguments_block (token *arguments_tok, tree_args_block_attribute_list *attr_list, tree_args_block_validation_list *validation_list, token *end_tok, comment_list *lc, comment_list *tc) { tree_arguments_block *retval = nullptr; if (end_token_ok (end_tok, token::arguments_end)) { filepos beg_pos = arguments_tok->beg_pos (); int l = beg_pos.line (); int c = beg_pos.column (); retval = new tree_arguments_block (attr_list, validation_list, l, c); } else { delete attr_list; delete validation_list; delete lc; delete tc; } return retval; } tree_arg_validation * base_parser::make_arg_validation (tree_arg_size_spec *size_spec, tree_identifier *class_name, tree_arg_validation_fcns *validation_fcns, tree_expression *default_value) { // FIXME: Validate arguments and convert to more specific types // (std::string for arg_name and class_name, etc). return new tree_arg_validation (size_spec, class_name, validation_fcns, default_value); } tree_args_block_attribute_list * base_parser::make_args_attribute_list (tree_identifier *attribute_name) { // FIXME: Validate argument and convert to more specific type // (std::string for attribute_name). return new tree_args_block_attribute_list (attribute_name); } tree_args_block_validation_list * base_parser::make_args_validation_list (tree_arg_validation *arg_validation) { return new tree_args_block_validation_list (arg_validation); } tree_args_block_validation_list * base_parser::append_args_validation_list (tree_args_block_validation_list *list, tree_arg_validation *arg_validation) { list->append (arg_validation); return list; } tree_arg_size_spec * base_parser::make_arg_size_spec (tree_argument_list *size_args) { // FIXME: Validate argument. return new tree_arg_size_spec (size_args); } tree_arg_validation_fcns * base_parser::make_arg_validation_fcns (tree_argument_list *fcn_args) { // FIXME: Validate argument. return new tree_arg_validation_fcns (fcn_args); } void base_parser::recover_from_parsing_function (void) { m_lexer.m_symtab_context.pop (); if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0 && ! m_parsing_subfunctions) m_parsing_subfunctions = true; m_curr_fcn_depth--; m_function_scopes.pop (); m_lexer.m_defining_func--; m_lexer.m_parsed_function_name.pop (); m_lexer.m_looking_at_return_list = false; m_lexer.m_looking_at_parameter_list = false; } // A CLASSDEF block defines a class that has a constructor and other // methods, but it is not an executable command. Parsing the block // makes some changes in the symbol table (inserting the constructor // and methods, and adding to the list of known objects) and creates // a parse tree containing meta information about the class. // LC contains comments appearing before the classdef keyword. // TC contains comments appearing between the classdef elements // and the final end token for the classdef block. tree_classdef * base_parser::make_classdef (token *tok_val, tree_classdef_attribute_list *a, tree_identifier *id, tree_classdef_superclass_list *sc, tree_classdef_body *body, token *end_tok, comment_list *lc, comment_list *tc) { tree_classdef *retval = nullptr; m_lexer.m_symtab_context.pop (); std::string cls_name = id->name (); std::string nm = m_lexer.m_fcn_file_name; std::size_t pos = nm.find_last_of (sys::file_ops::dir_sep_chars ()); if (pos != std::string::npos) nm = m_lexer.m_fcn_file_name.substr (pos+1); if (nm != cls_name) { int l = id->line (); int c = id->column (); delete a; delete id; delete sc; delete body; delete lc; delete tc; bison_error ("invalid classdef definition, the class name must match the filename", l, c); } else { if (end_token_ok (end_tok, token::classdef_end)) { int l = tok_val->line (); int c = tok_val->column (); if (! body) body = new tree_classdef_body (); retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (), a, id, sc, body, lc, tc, m_curr_package_name, l, c); } else { delete a; delete id; delete sc; delete body; delete lc; delete tc; end_token_error (end_tok, token::switch_end); } } return retval; } // LC contains comments appearing before the properties keyword. // If this properties block appears first in the list of classdef // elements, this comment list will be used for the help text for the // classdef block. // TC contains comments appearing between the list of properties // and the final end token for the properties block and may be used to // find the doc string for the final property in the list. tree_classdef_properties_block * base_parser::make_classdef_properties_block (token *tok_val, tree_classdef_attribute_list *a, tree_classdef_property_list *plist, token *end_tok, comment_list *lc, comment_list *tc) { tree_classdef_properties_block *retval = nullptr; if (end_token_ok (end_tok, token::properties_end)) { int l = tok_val->line (); int c = tok_val->column (); if (plist) { // If the element at the end of the list doesn't have a doc // string, see whether the first element of TC is an // end-of-line comment for us to use. if (tc) { tree_classdef_property *last_elt = plist->back (); if (! last_elt->have_doc_string ()) { comment_elt first_comment_elt = tc->front (); if (first_comment_elt.is_end_of_line ()) { std::string eol_comment = first_comment_elt.text (); last_elt->doc_string (eol_comment); } } } } else plist = new tree_classdef_property_list (); retval = new tree_classdef_properties_block (a, plist, lc, tc, l, c); } else { delete a; delete plist; delete lc; delete tc; end_token_error (end_tok, token::properties_end); } return retval; } // LC contains comments appearing before the methods keyword. // If this methods block appears first in the list of classdef // elements, this comment list will be used for the help text for the // classdef block. tree_classdef_methods_block * base_parser::make_classdef_methods_block (token *tok_val, tree_classdef_attribute_list *a, tree_classdef_methods_list *mlist, token *end_tok, comment_list *lc, comment_list *tc) { tree_classdef_methods_block *retval = nullptr; if (end_token_ok (end_tok, token::methods_end)) { int l = tok_val->line (); int c = tok_val->column (); if (! mlist) mlist = new tree_classdef_methods_list (); retval = new tree_classdef_methods_block (a, mlist, lc, tc, l, c); } else { delete a; delete mlist; delete lc; delete tc; end_token_error (end_tok, token::methods_end); } return retval; } // LC contains comments appearing before the events keyword. // If this events block appears first in the list of classdef // elements, this comment list will be used for the help text for the // classdef block. // TC contains comments appearing between the list of events and // the final end token for the events block and may be used to find // the doc string for the final event in the list. tree_classdef_events_block * base_parser::make_classdef_events_block (token *tok_val, tree_classdef_attribute_list *a, tree_classdef_events_list *elist, token *end_tok, comment_list *lc, comment_list *tc) { tree_classdef_events_block *retval = nullptr; if (end_token_ok (end_tok, token::events_end)) { int l = tok_val->line (); int c = tok_val->column (); if (! elist) elist = new tree_classdef_events_list (); retval = new tree_classdef_events_block (a, elist, lc, tc, l, c); } else { delete a; delete elist; delete lc; delete tc; end_token_error (end_tok, token::events_end); } return retval; } // LC contains comments appearing before the enumeration keyword. // If this enumeration block appears first in the list of classdef // elements, this comment list will be used for the help text for the // classdef block. // TC contains comments appearing between the list of // enumerations and the final end token for the enumeration block and // may be used to find the doc string for the final enumeration in the // list. tree_classdef_enum_block * base_parser::make_classdef_enum_block (token *tok_val, tree_classdef_attribute_list *a, tree_classdef_enum_list *elist, token *end_tok, comment_list *lc, comment_list *tc) { tree_classdef_enum_block *retval = nullptr; if (end_token_ok (end_tok, token::enumeration_end)) { int l = tok_val->line (); int c = tok_val->column (); if (! elist) elist = new tree_classdef_enum_list (); retval = new tree_classdef_enum_block (a, elist, lc, tc, l, c); } else { delete a; delete elist; delete lc; delete tc; end_token_error (end_tok, token::enumeration_end); } return retval; } octave_user_function* base_parser::start_classdef_external_method (tree_identifier *id, tree_parameter_list *pl) { octave_user_function* retval = nullptr; // External methods are only allowed within @-folders. In this case, // m_curr_class_name will be non-empty. if (! m_curr_class_name.empty ()) { std::string mname = id->name (); // Methods that cannot be declared outside the classdef file: // - methods with '.' character (e.g. property accessors) // - class constructor // - 'delete' if (mname.find_first_of (".") == std::string::npos && mname != "delete" && mname != m_curr_class_name) { // Create a dummy function that is used until the real method // is loaded. retval = new octave_user_function (symbol_scope (), pl); retval->stash_function_name (mname); int l = id->line (); int c = id->column (); retval->stash_fcn_location (l, c); } else bison_error ("invalid external method declaration, an external " "method cannot be the class constructor, 'delete' " "or have a dot (.) character in its name"); } else bison_error ("external methods are only allowed in @-folders"); if (! retval) delete id; return retval; } tree_function_def * base_parser::finish_classdef_external_method (octave_user_function *fcn, tree_parameter_list *ret_list, comment_list *cl) { if (! ret_list) ret_list = new tree_parameter_list (tree_parameter_list::out); fcn->define_ret_list (ret_list); if (cl) fcn->stash_leading_comment (cl); int l = fcn->beginning_line (); int c = fcn->beginning_column (); return new tree_function_def (fcn, l, c); } bool base_parser::finish_classdef_file (tree_classdef *cls, tree_statement_list *local_fcns) { parse_tree_validator validator; cls->accept (validator); if (local_fcns) { for (tree_statement *elt : *local_fcns) { tree_command *cmd = elt->command (); tree_function_def *fcn_def = dynamic_cast<tree_function_def *> (cmd); fcn_def->accept (validator); } } if (! validator.ok ()) { delete cls; delete local_fcns; bison_error (validator.error_list ()); return false; } // Require all validations to succeed before installing any local // functions or defining the classdef object for later use. if (local_fcns) { symbol_table& symtab = __get_symbol_table__ ("base_parser::finish_classdef_file"); for (tree_statement *elt : *local_fcns) { tree_command *cmd = elt->command (); tree_function_def *fcn_def = dynamic_cast<tree_function_def *> (cmd); octave_value ov_fcn = fcn_def->function (); octave_user_function *fcn = ov_fcn.user_function_value (); std::string nm = fcn->name (); std::string file = fcn->fcn_file_name (); symtab.install_local_function (nm, ov_fcn, file); } delete local_fcns; } // FIXME: Is it possible for the following condition to be false? if (m_lexer.m_reading_classdef_file) m_classdef_object = std::shared_ptr<tree_classdef> (cls); return true; } // Make an index expression. tree_index_expression * base_parser::make_index_expression (tree_expression *expr, tree_argument_list *args, char type) { tree_index_expression *retval = nullptr; if (args && args->has_magic_tilde ()) { delete expr; delete args; bison_error ("invalid use of empty argument (~) in index expression"); } else { int l = expr->line (); int c = expr->column (); if (! expr->is_postfix_indexed ()) expr->set_postfix_index (type); if (expr->is_index_expression ()) { tree_index_expression *tmp = dynamic_cast<tree_index_expression *> (expr); 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 = dynamic_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 = dynamic_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 (iskeyword (name)) { bison_error ("invalid use of keyword '" + name + "' in parameter list"); return false; } else if (dict.find (name) != dict.end ()) { bison_error ("'" + name + "' appears more than once in parameter list"); return false; } else dict.insert (name); } } std::string va_type = (type == tree_parameter_list::in ? "varargin" : "varargout"); std::size_t len = lst->length (); if (len > 0) { tree_decl_elt *elt = lst->back (); tree_identifier *id = elt->ident (); if (id && id->name () == va_type) { if (len == 1) lst->mark_varargs_only (); else lst->mark_varargs (); tree_parameter_list::iterator p = lst->end (); --p; delete *p; lst->erase (p); } } return true; } bool base_parser::validate_array_list (tree_expression *e) { bool retval = true; tree_array_list *al = dynamic_cast<tree_array_list *> (e); for (tree_argument_list* row : *al) { if (row && row->has_magic_tilde ()) { retval = false; if (e->is_matrix ()) bison_error ("invalid use of tilde (~) in matrix expression"); else bison_error ("invalid use of tilde (~) in cell expression"); break; } } return retval; } tree_argument_list * base_parser::validate_matrix_for_assignment (tree_expression *e) { tree_argument_list *retval = nullptr; if (e->is_constant ()) { tree_evaluator& tw = __get_evaluator__ ("validate_matrix_for_assignment"); octave_value ov = e->evaluate (tw); delete e; if (ov.isempty ()) bison_error ("invalid empty left hand side of assignment"); else bison_error ("invalid constant left hand side of assignment"); } else { bool is_simple_assign = true; tree_argument_list *tmp = nullptr; if (e->is_matrix ()) { tree_matrix *mat = dynamic_cast<tree_matrix *> (e); if (mat && mat->size () == 1) { tmp = mat->front (); mat->pop_front (); delete e; is_simple_assign = false; } } else tmp = new tree_argument_list (e); if (tmp && tmp->is_valid_lvalue_list ()) { m_lexer.mark_as_variables (tmp->variable_names ()); retval = tmp; } else { delete tmp; bison_error ("invalid left hand side of assignment"); } if (retval && is_simple_assign) retval->mark_as_simple_assign_lhs (); } return retval; } // Finish building an array_list. tree_expression * base_parser::finish_array_list (tree_array_list *array_list, token */*open_delim*/, token *close_delim) { tree_expression *retval = array_list; array_list->set_location (close_delim->line (), close_delim->column ()); if (array_list->all_elements_are_constant ()) { interpreter& interp = __get_interpreter__ ("finish_array_list"); try { // If the evaluation generates a warning message, restore // the previous value of last_warning_message and skip the // conversion to a constant value. error_system& es = interp.get_error_system (); unwind_action restore_last_warning_message (&error_system::set_last_warning_message, &es, es.last_warning_message ("")); unwind_action restore_discard_warning_messages (&error_system::set_discard_warning_messages, &es, es.discard_warning_messages (true)); tree_evaluator& tw = interp.get_evaluator (); octave_value tmp = array_list->evaluate (tw); std::string msg = es.last_warning_message (); if (msg.empty ()) { tree_constant *tc_retval = new tree_constant (tmp, close_delim->line (), close_delim->column ()); std::ostringstream buf; tree_print_code tpc (buf); array_list->accept (tpc); tc_retval->stash_original_text (buf.str ()); delete array_list; retval = tc_retval; } } catch (const execution_exception&) { interp.recover_from_exception (); } } return retval; } // Finish building a matrix list. tree_expression * base_parser::finish_matrix (tree_matrix *m, token *open_delim, token *close_delim) { return (m ? finish_array_list (m, open_delim, close_delim) : new tree_constant (octave_null_matrix::instance, close_delim->line (), close_delim->column ())); } // Finish building a cell list. tree_expression * base_parser::finish_cell (tree_cell *c, token *open_delim, token *close_delim) { return (c ? finish_array_list (c, open_delim, close_delim) : new tree_constant (octave_value (Cell ()), close_delim->line (), close_delim->column ())); } tree_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::disallow_command_syntax (void) { m_lexer.m_allow_command_syntax = false; } // FIXME: this function partially duplicates do_dbtype in debug.cc. static std::string get_file_line (const std::string& name, int line) { // NAME should be an absolute file name and the file should exist. std::ifstream fs = sys::ifstream (name.c_str (), std::ios::in); std::string text; if (fs) { int i = 1; do { if (! std::getline (fs, text)) { text = ""; break; } } while (i++ < line); } return text; } void base_parser::bison_error (const std::string& str) { bison_error (str, m_lexer.m_filepos); } void base_parser::bison_error (const std::string& str, const filepos& pos) { bison_error (str, pos.line (), pos.column ()); } void base_parser::bison_error (const std::string& str, int err_line, int err_col) { std::ostringstream output_buf; if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_script_file || m_lexer.m_reading_classdef_file) output_buf << "parse error near line " << err_line << " of file " << m_lexer.m_fcn_file_full_name; else output_buf << "parse error:"; if (str != "parse error") output_buf << "\n\n " << str; output_buf << "\n\n"; std::string curr_line; if (m_lexer.m_reading_fcn_file || m_lexer.m_reading_script_file || m_lexer.m_reading_classdef_file) curr_line = get_file_line (m_lexer.m_fcn_file_full_name, err_line); else curr_line = m_lexer.m_current_input_line; // Adjust the error column for display because it is 1-based in the // lexer for easier reporting. err_col--; if (! curr_line.empty ()) { // FIXME: we could do better if we just cached lines from the // input file in a list. See also functions for managing input // buffers in lex.ll. std::size_t len = curr_line.length (); if (curr_line[len-1] == '\n') curr_line.resize (len-1); // Print the line, maybe with a pointer near the error token. output_buf << ">>> " << curr_line << "\n"; if (err_col == 0) err_col = len; for (int i = 0; i < err_col + 3; i++) output_buf << " "; output_buf << "^"; } output_buf << "\n"; m_parse_error_msg = output_buf.str (); } void base_parser::bison_error (const parse_exception& pe) { bison_error (pe.message (), pe.line (), pe.column ()); } void base_parser::bison_error (const std::list<parse_exception>& pe_list) { // For now, we just report the first error found. Reporting all // errors will require a bit more refactoring. parse_exception pe = pe_list.front (); bison_error (pe.message (), pe.line (), pe.column ()); } int parser::run (void) { int status = -1; yypstate *pstate = static_cast<yypstate *> (m_parser_state); try { status = octave_pull_parse (pstate, *this); } catch (const execution_exception&) { // FIXME: In previous versions, we emitted a parse error here // but that is not always correct because the error could have // happened inside a GUI callback functions executing in the // readline event_hook loop. Maybe we need a separate exception // class for parse errors? throw; } catch (const exit_exception&) { throw; } catch (const interrupt_exception&) { throw; } catch (...) { std::string file = m_lexer.m_fcn_file_full_name; if (file.empty ()) error ("unexpected exception while parsing input"); else error ("unexpected exception while parsing %s", file.c_str ()); } if (status != 0) parse_error ("%s", m_parse_error_msg.c_str ()); return status; } // Parse input from INPUT. Pass TRUE for EOF if the end of INPUT should // finish the parse. int push_parser::run (const std::string& input, bool eof) { int status = -1; dynamic_cast<push_lexer&> (m_lexer).append_input (input, eof); do { YYSTYPE lval; int token = octave_lex (&lval, m_lexer.m_scanner); if (token < 0) { // TOKEN == -2 means that the lexer recognized a comment // and we should be at the end of the buffer but not the // end of the file so we should return 0 to indicate // "complete input" instead of -1 to request more input. status = (token == -2 ? 0 : -1); if (! eof && m_lexer.at_end_of_buffer ()) return status; break; } yypstate *pstate = static_cast<yypstate *> (m_parser_state); try { status = octave_push_parse (pstate, token, &lval, *this); } catch (execution_exception& e) { std::string file = m_lexer.m_fcn_file_full_name; if (file.empty ()) error (e, "parse error"); else error (e, "parse error in %s", file.c_str ()); } catch (const exit_exception&) { throw; } catch (interrupt_exception &) { throw; } catch (...) { std::string file = m_lexer.m_fcn_file_full_name; if (file.empty ()) error ("unexpected exception while parsing input"); else error ("unexpected exception while parsing %s", file.c_str ()); } } while (status == YYPUSH_MORE || ! m_lexer.at_end_of_buffer ()); if (status != 0) parse_error ("%s", m_parse_error_msg.c_str ()); return status; } int push_parser::run (void) { if (! m_reader) error ("push_parser::run requires valid input_reader"); int exit_status = 0; input_system& input_sys = m_interpreter.get_input_system (); std::string prompt = command_editor::decode_prompt_string (input_sys.PS1 ()); do { // Reset status each time through the read loop so that // it won't be set to -1 and cause us to exit the outer // loop early if there is an exception while reading // input or parsing. exit_status = 0; bool eof = false; std::string input_line = m_reader->get_input (prompt, eof); if (eof) { exit_status = EOF; break; } exit_status = run (input_line, false); prompt = command_editor::decode_prompt_string (input_sys.PS2 ()); } while (exit_status < 0); return exit_status; } octave_value parse_fcn_file (interpreter& interp, const std::string& full_file, const std::string& file, const std::string& dir_name, const std::string& dispatch_type, const std::string& package_name, bool require_file, bool force_script, bool autoload, bool relative_lookup) { octave_value retval; FILE *ffile = nullptr; if (! full_file.empty ()) { // Check that m-file is not overly large which can segfault interpreter. const int max_file_size = 512 * 1024 * 1024; // 512 MB sys::file_stat fs (full_file); if (fs && fs.size () > max_file_size) { error ("file '%s' is too large, > 512 MB", full_file.c_str ()); return octave_value (); } ffile = sys::fopen (full_file, "rb"); } if (! ffile) { if (require_file) error ("no such file, '%s'", full_file.c_str ()); return octave_value (); } unwind_action act ([=] (void) { ::fclose (ffile); }); // get the encoding for this folder input_system& input_sys = interp.get_input_system (); parser parser (ffile, interp, input_sys.dir_encoding (dir_name)); parser.m_curr_class_name = dispatch_type; parser.m_curr_package_name = package_name; parser.m_autoloading = autoload; parser.m_fcn_file_from_relative_lookup = relative_lookup; parser.m_lexer.m_force_script = force_script; parser.m_lexer.prep_for_file (); parser.m_lexer.m_parsing_class_method = ! dispatch_type.empty (); parser.m_lexer.m_fcn_file_name = file; parser.m_lexer.m_fcn_file_full_name = full_file; parser.m_lexer.m_dir_name = dir_name; parser.m_lexer.m_package_name = package_name; int err = parser.run (); if (err) error ("parse error while reading file %s", full_file.c_str ()); octave_value ov_fcn = parser.m_primary_fcn; if (parser.m_lexer.m_reading_classdef_file && parser.classdef_object ()) { // Convert parse tree for classdef object to // meta.class info (and stash it in the symbol // table?). Return pointer to constructor? if (ov_fcn.is_defined ()) panic_impossible (); bool is_at_folder = ! dispatch_type.empty (); std::shared_ptr<tree_classdef> cdef_obj = parser.classdef_object(); return cdef_obj->make_meta_class (interp, is_at_folder); } else if (ov_fcn.is_defined ()) { octave_function *fcn = ov_fcn.function_value (); fcn->maybe_relocate_end (); if (parser.m_parsing_subfunctions) { if (! parser.m_endfunction_found) parser.m_subfunction_names.reverse (); fcn->stash_subfunction_names (parser.m_subfunction_names); } return ov_fcn; } return octave_value (); } bool base_parser::finish_input (tree_statement_list *lst, bool at_eof) { m_lexer.m_end_of_input = at_eof; if (lst) { parse_tree_validator validator; lst->accept (validator); if (! validator.ok ()) { delete lst; bison_error (validator.error_list ()); return false; } } std::shared_ptr<tree_statement_list> tmp_lst (lst); statement_list (tmp_lst); return true; } // Check script or function for semantic errors. bool base_parser::validate_primary_fcn (void) { octave_user_code *code = m_primary_fcn.user_code_value (); if (code) { parse_tree_validator validator; code->accept (validator); if (! validator.ok ()) { bison_error (validator.error_list ()); return false; } } return true; } // Maybe print a warning if an assignment expression is used as the // test in a logical expression. void base_parser::maybe_warn_assign_as_truth_value (tree_expression *expr) { if (expr->is_assignment_expression () && expr->paren_count () < 2) { if (m_lexer.m_fcn_file_full_name.empty ()) warning_with_id ("Octave:assign-as-truth-value", "suggest parenthesis around assignment used as truth value"); else warning_with_id ("Octave:assign-as-truth-value", "suggest parenthesis around assignment used as truth value near line %d, column %d in file '%s'", expr->line (), expr->column (), m_lexer.m_fcn_file_full_name.c_str ()); } } // Maybe print a warning about switch labels that aren't constants. void base_parser::maybe_warn_variable_switch_label (tree_expression *expr) { if (! expr->is_constant ()) { if (m_lexer.m_fcn_file_full_name.empty ()) warning_with_id ("Octave:variable-switch-label", "variable switch label"); else warning_with_id ("Octave:variable-switch-label", "variable switch label near line %d, column %d in file '%s'", expr->line (), expr->column (), m_lexer.m_fcn_file_full_name.c_str ()); } } void base_parser::maybe_warn_missing_semi (tree_statement_list *t) { if (m_curr_fcn_depth >= 0) { tree_statement *tmp = t->back (); if (tmp->is_expression ()) warning_with_id ("Octave:missing-semicolon", "missing semicolon near line %d, column %d in file '%s'", tmp->line (), tmp->column (), m_lexer.m_fcn_file_full_name.c_str ()); } } std::string get_help_from_file (const std::string& nm, bool& symbol_found, std::string& full_file) { std::string retval; full_file = fcn_file_in_path (nm); std::string file = full_file; std::size_t file_len = file.length (); if ((file_len > 4 && file.substr (file_len-4) == ".oct") || (file_len > 4 && file.substr (file_len-4) == ".mex") || (file_len > 2 && file.substr (file_len-2) == ".m")) { file = sys::env::base_pathname (file); file = file.substr (0, file.find_last_of ('.')); std::size_t pos = file.find_last_of (sys::file_ops::dir_sep_str ()); if (pos != std::string::npos) file = file.substr (pos+1); } if (! file.empty ()) { interpreter& interp = __get_interpreter__ ("get_help_from_file"); symbol_found = true; octave_value ov_fcn = parse_fcn_file (interp, full_file, file, "", "", "", true, false, false, false); if (ov_fcn.is_defined ()) { octave_function *fcn = ov_fcn.function_value (); if (fcn) retval = fcn->doc_string (); } } return retval; } std::string get_help_from_file (const std::string& nm, bool& symbol_found) { std::string file; return get_help_from_file (nm, symbol_found, file); } octave_value load_fcn_from_file (const std::string& file_name, const std::string& dir_name, const std::string& dispatch_type, const std::string& package_name, const std::string& fcn_name, bool autoload) { octave_value retval; unwind_protect frame; std::string nm = file_name; std::size_t nm_len = nm.length (); std::string file; bool relative_lookup = false; file = nm; if ((nm_len > 4 && nm.substr (nm_len-4) == ".oct") || (nm_len > 4 && nm.substr (nm_len-4) == ".mex") || (nm_len > 2 && nm.substr (nm_len-2) == ".m")) { nm = sys::env::base_pathname (file); nm = nm.substr (0, nm.find_last_of ('.')); std::size_t pos = nm.find_last_of (sys::file_ops::dir_sep_str ()); if (pos != std::string::npos) nm = nm.substr (pos+1); } relative_lookup = ! sys::env::absolute_pathname (file); file = sys::env::make_absolute (file); int len = file.length (); interpreter& interp = __get_interpreter__ ("load_fcn_from_file"); dynamic_loader& dyn_loader = interp.get_dynamic_loader (); if (len > 4 && file.substr (len-4, len-1) == ".oct") { if (autoload && ! fcn_name.empty ()) nm = fcn_name; octave_function *tmpfcn = dyn_loader.load_oct (nm, file, relative_lookup); if (tmpfcn) { tmpfcn->stash_package_name (package_name); retval = octave_value (tmpfcn); } } else if (len > 4 && file.substr (len-4, len-1) == ".mex") { // Temporarily load m-file version of mex-file, if it exists, // to get the help-string to use. std::string doc_string; octave_value ov_fcn = parse_fcn_file (interp, file.substr (0, len - 2), nm, dir_name, dispatch_type, package_name, false, autoload, autoload, relative_lookup); if (ov_fcn.is_defined ()) { octave_function *tmpfcn = ov_fcn.function_value (); if (tmpfcn) doc_string = tmpfcn->doc_string (); } octave_function *tmpfcn = dyn_loader.load_mex (nm, file, relative_lookup); if (tmpfcn) { tmpfcn->document (doc_string); tmpfcn->stash_package_name (package_name); retval = octave_value (tmpfcn); } } else if (len > 2) { retval = parse_fcn_file (interp, file, nm, dir_name, dispatch_type, package_name, true, autoload, autoload, relative_lookup); } return retval; } DEFMETHOD (autoload, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {@var{autoload_map} =} autoload () @deftypefnx {} {} autoload (@var{function}, @var{file}) @deftypefnx {} {} autoload (@dots{}, "remove") Define @var{function} to autoload from @var{file}. The second argument, @var{file}, should be an absolute filename or a file name in the same directory as the function or script from which the autoload command was run. @var{file} @emph{should not} depend on the Octave load path. Normally, calls to @code{autoload} appear in PKG_ADD script files that are evaluated when a directory is added to Octave's load path. To avoid having to hardcode directory names in @var{file}, if @var{file} is in the same directory as the PKG_ADD script then @example autoload ("foo", "bar.oct"); @end example @noindent will load the function @code{foo} from the file @code{bar.oct}. The above usage when @code{bar.oct} is not in the same directory, or usages such as @example autoload ("foo", file_in_loadpath ("bar.oct")) @end example @noindent are strongly discouraged, as their behavior may be unpredictable. With no arguments, return a structure containing the current autoload map. If a third argument @qcode{"remove"} is given, the function is cleared and not loaded anymore during the current Octave session. @seealso{PKG_ADD} @end deftypefn */) { int nargin = args.length (); if (nargin == 1 || nargin > 3) print_usage (); tree_evaluator& tw = interp.get_evaluator (); if (nargin == 0) return octave_value (tw.get_autoload_map ()); else { string_vector argv = args.make_argv ("autoload"); if (nargin == 2) tw.add_autoload (argv[1], argv[2]); else if (nargin == 3) { if (argv[3] != "remove") error_with_id ("Octave:invalid-input-arg", "autoload: third argument can only be 'remove'"); tw.remove_autoload (argv[1], argv[2]); } } return octave_value_list (); } DEFMETHOD (mfilename, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} mfilename () @deftypefnx {} {} mfilename ("fullpath") @deftypefnx {} {} mfilename ("fullpathext") Return the name of the currently executing file. The base name of the currently executing script or function is returned without any extension. If called from outside an m-file, such as the command line, return the empty string. Given the argument @qcode{"fullpath"}, include the directory part of the filename, but not the extension. Given the argument @qcode{"fullpathext"}, include the directory part of the filename and the extension. @seealso{inputname, dbstack} @end deftypefn */) { int nargin = args.length (); if (nargin > 1) print_usage (); std::string opt; if (nargin == 1) opt = args(0).xstring_value ("mfilename: option argument must be a string"); return octave_value (interp.mfilename (opt)); } // Execute the contents of a script file. For compatibility with // Matlab, also execute a function file by calling the function it // defines with no arguments and nargout = 0. void source_file (const std::string& file_name, const std::string& context, bool verbose, bool require_file) { interpreter& interp = __get_interpreter__ ("source_file"); interp.source_file (file_name, context, verbose, require_file); } DEFMETHOD (source, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} source (@var{file}) @deftypefnx {} {} source (@var{file}, @var{context}) Parse and execute the contents of @var{file}. Without specifying @var{context}, this is equivalent to executing commands from a script file, but without requiring the file to be named @file{@var{file}.m} or to be on the execution path. Instead of the current context, the script may be executed in either the context of the function that called the present function (@qcode{"caller"}), or the top-level context (@qcode{"base"}). @seealso{run} @end deftypefn */) { int nargin = args.length (); if (nargin < 1 || nargin > 2) print_usage (); std::string file_name = args(0).xstring_value ("source: FILE must be a string"); std::string context; if (nargin == 2) context = args(1).xstring_value ("source: CONTEXT must be a string"); interp.source_file (file_name, context); return octave_value_list (); } //! Evaluate an Octave function (built-in or interpreted) and return //! the list of result values. //! //! @param name The name of the function to call. //! @param args The arguments to the function. //! @param nargout The number of output arguments expected. //! @return A list of output values. The length of the list is not //! necessarily the same as @c nargout. octave_value_list feval (const char *name, const octave_value_list& args, int nargout) { interpreter& interp = __get_interpreter__ ("feval"); return interp.feval (name, args, nargout); } octave_value_list feval (const std::string& name, const octave_value_list& args, int nargout) { interpreter& interp = __get_interpreter__ ("feval"); return interp.feval (name, args, nargout); } octave_value_list feval (octave_function *fcn, const octave_value_list& args, int nargout) { interpreter& interp = __get_interpreter__ ("feval"); return interp.feval (fcn, args, nargout); } octave_value_list feval (const octave_value& val, const octave_value_list& args, int nargout) { interpreter& interp = __get_interpreter__ ("feval"); return interp.feval (val, args, nargout); } octave_value_list feval (const octave_value_list& args, int nargout) { interpreter& interp = __get_interpreter__ ("feval"); return interp.feval (args, nargout); } DEFMETHOD (feval, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {} feval (@var{name}, @dots{}) Evaluate the function named @var{name}. Any arguments after the first are passed as inputs to the named function. For example, @example @group feval ("acos", -1) @result{} 3.1416 @end group @end example @noindent calls the function @code{acos} with the argument @samp{-1}. The function @code{feval} can also be used with function handles of any sort (@pxref{Function Handles}). Historically, @code{feval} was the only way to call user-supplied functions in strings, but function handles are now preferred due to the cleaner syntax they offer. For example, @example @group @var{f} = @@exp; feval (@var{f}, 1) @result{} 2.7183 @var{f} (1) @result{} 2.7183 @end group @end example @noindent are equivalent ways to call the function referred to by @var{f}. If it cannot be predicted beforehand whether @var{f} is a function handle, function name in a string, or inline function then @code{feval} can be used instead. @end deftypefn */) { if (args.length () == 0) print_usage (); return interp.feval (args, nargout); } DEFMETHOD (builtin, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {[@dots{}] =} builtin (@var{f}, @dots{}) Call the base function @var{f} even if @var{f} is overloaded to another function for the given type signature. This is normally useful when doing object-oriented programming and there is a requirement to call one of Octave's base functions rather than the overloaded one of a new class. A trivial example which redefines the @code{sin} function to be the @code{cos} function shows how @code{builtin} works. @example @group sin (0) @result{} 0 function y = sin (x), y = cos (x); endfunction sin (0) @result{} 1 builtin ("sin", 0) @result{} 0 @end group @end example @end deftypefn */) { octave_value_list retval; if (args.length () == 0) print_usage (); const std::string name (args(0).xstring_value ("builtin: function name (F) must be a string")); symbol_table& symtab = interp.get_symbol_table (); octave_value fcn = symtab.builtin_find (name); if (fcn.is_defined ()) retval = interp.feval (fcn.function_value (), args.splice (0, 1), nargout); else error ("builtin: lookup for symbol '%s' failed", name.c_str ()); return retval; } void cleanup_statement_list (tree_statement_list **lst) { if (*lst) { delete *lst; *lst = nullptr; } } DEFMETHOD (eval, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {} eval (@var{try}) @deftypefnx {} {} eval (@var{try}, @var{catch}) Parse the string @var{try} and evaluate it as if it were an Octave program. If execution fails, evaluate the optional string @var{catch}. The string @var{try} is evaluated in the current context, so any results remain available after @code{eval} returns. The following example creates the variable @var{A} with the approximate value of 3.1416 in the current workspace. @example eval ("A = acos(-1);"); @end example If an error occurs during the evaluation of @var{try} then the @var{catch} string is evaluated, as the following example shows: @example @group eval ('error ("This is a bad example");', 'printf ("This error occurred:\n%s\n", lasterr ());'); @print{} This error occurred: This is a bad example @end group @end example Programming Note: if you are only using @code{eval} as an error-capturing mechanism, rather than for the execution of arbitrary code strings, Consider using try/catch blocks or unwind_protect/unwind_protect_cleanup blocks instead. These techniques have higher performance and don't introduce the security considerations that the evaluation of arbitrary code does. @seealso{evalin, evalc, assignin, feval} @end deftypefn */) { int nargin = args.length (); if (nargin < 1 || nargin > 2) print_usage (); if (! args(0).is_string () || args(0).rows () > 1 || args(0).ndims () != 2) error ("eval: TRY must be a string"); std::string try_code = args(0).string_value (); if (nargin == 1) return interp.eval (try_code, nargout); else { if (! args(1).is_string () || args(1).rows () > 1 || args(1).ndims () != 2) error ("eval: CATCH must be a string"); std::string catch_code = args(1).string_value (); return interp.eval (try_code, catch_code, nargout); } } /* %!shared x %! x = 1; %!assert (eval ("x"), 1) %!assert (eval ("x;")) %!assert (eval ("x;"), 1) %!test %! y = eval ("x"); %! assert (y, 1); %!test %! y = eval ("x;"); %! assert (y, 1); %!test %! eval ("x = 1;"); %! assert (x,1); %!test %! eval ("flipud = 2;"); %! assert (flipud, 2); %!function y = __f () %! eval ("flipud = 2;"); %! y = flipud; %!endfunction %!assert (__f(), 2) %!test <*35645> %! [a,] = gcd (1,2); %! [a,b,] = gcd (1, 2); ## Can't assign to a keyword %!error eval ("switch = 13;") %!shared str %! str = "disp ('hello');"; %! str(:,:,2) = str(:,:,1); %!error <TRY must be a string> eval (1) %!error <TRY must be a string> eval (['a';'b']) %!error <TRY must be a string> eval (str) %!error <CATCH must be a string> eval (str(:,:,1), 1) %!error <CATCH must be a string> eval (str(:,:,1), ['a';'b']) %!error <CATCH must be a string> eval (str(:,:,1), str) */ DEFMETHOD (assignin, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} assignin (@var{context}, @var{varname}, @var{value}) Assign @var{value} to @var{varname} in context @var{context}, which may be either @qcode{"base"} or @qcode{"caller"}. @seealso{evalin} @end deftypefn */) { if (args.length () != 3) print_usage (); std::string context = args(0).xstring_value ("assignin: CONTEXT must be a string"); std::string varname = args(1).xstring_value ("assignin: VARNAME must be a string"); interp.assignin (context, varname, args(2)); return octave_value_list (); } /* %!error assignin ("base", "switch", "13") */ DEFMETHOD (evalin, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {} evalin (@var{context}, @var{try}) @deftypefnx {} {} evalin (@var{context}, @var{try}, @var{catch}) Like @code{eval}, except that the expressions are evaluated in the context @var{context}, which may be either @qcode{"caller"} or @qcode{"base"}. @seealso{eval, assignin} @end deftypefn */) { int nargin = args.length (); if (nargin < 2 || nargin > 3) print_usage (); std::string context = args(0).xstring_value ("evalin: CONTEXT must be a string"); std::string try_code = args(1).xstring_value ("evalin: TRY must be a string"); if (nargin == 3) { std::string catch_code = args(2).xstring_value ("evalin: CATCH must be a string"); return interp.evalin (context, try_code, catch_code, nargout); } return interp.evalin (context, try_code, nargout); } DEFMETHOD (evalc, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {@var{s} =} evalc (@var{try}) @deftypefnx {} {@var{s} =} evalc (@var{try}, @var{catch}) Parse and evaluate the string @var{try} as if it were an Octave program, while capturing the output into the return variable @var{s}. If execution fails, evaluate the optional string @var{catch}. This function behaves like @code{eval}, but any output or warning messages which would normally be written to the console are captured and returned in the string @var{s}. The @code{diary} is disabled during the execution of this function. When @code{system} is used, any output produced by external programs is @emph{not} captured, unless their output is captured by the @code{system} function itself. @example @group s = evalc ("t = 42"), t @result{} s = t = 42 @result{} t = 42 @end group @end example @seealso{eval, diary} @end deftypefn */) { int nargin = args.length (); if (nargin == 0 || nargin > 2) print_usage (); // Flush pending output and redirect stdout/stderr to capturing // buffer. octave_stdout.flush (); std::cerr.flush (); std::stringbuf buffer; std::streambuf *old_out_buf = octave_stdout.rdbuf (&buffer); std::streambuf *old_err_buf = std::cerr.rdbuf (&buffer); // Restore previous output buffers no matter how control exits this // function. There's no need to flush here. That has already // happened for the normal execution path. If an error happens during // the eval, then the message is stored in the exception object and we // will display it later, after the buffers have been restored. unwind_action act ([=] (void) { octave_stdout.rdbuf (old_out_buf); std::cerr.rdbuf (old_err_buf); }); // Call standard eval function. int eval_nargout = std::max (0, nargout - 1); octave_value_list retval = Feval (interp, args, eval_nargout); // Make sure we capture all pending output. octave_stdout.flush (); std::cerr.flush (); retval.prepend (buffer.str ()); return retval; } /* %!test %! [old_fmt, old_spacing] = format (); %! unwind_protect %! format short; %! str = evalc ("1"); %! assert (str, "ans = 1\n"); %! unwind_protect_cleanup %! format (old_fmt); %! format (old_spacing); %! end_unwind_protect %!assert (evalc ("1;"), "") %!test %! [s, y] = evalc ("1"); %! assert (s, ""); %! assert (y, 1); %!test %! [s, y] = evalc ("1;"); %! assert (s, ""); %! assert (y, 1); %!test %! [old_fmt, old_spacing] = format (); %! unwind_protect %! format short; %! str = evalc ("y = 2"); %! assert (str, "y = 2\n"); %! assert (y, 2); %! unwind_protect_cleanup %! format (old_fmt); %! format (old_spacing); %! end_unwind_protect %!test %! assert (evalc ("y = 3;"), ""); %! assert (y, 3); %!test %! [s, a, b] = evalc ("deal (1, 2)"); %! assert (s, ""); %! assert (a, 1); %! assert (b, 2); %!function [a, b] = __f_evalc () %! printf ("foo"); %! fprintf (stdout, "bar "); %! disp (pi); %! a = 1; %! b = 2; %!endfunction %!test %! [old_fmt, old_spacing] = format (); %! unwind_protect %! format short; %! [s, a, b] = evalc ("__f_evalc ()"); %! assert (s, "foobar 3.1416\n"); %! assert (a, 1); %! assert (b, 2); %! unwind_protect_cleanup %! format (old_fmt); %! format (old_spacing); %! end_unwind_protect %!error <foo> (evalc ("error ('foo')")) %!error <bar> (evalc ("error ('foo')", "error ('bar')")) %!test %! warning ("off", "quiet", "local"); %! str = evalc ("warning ('foo')"); %! assert (str(1:13), "warning: foo\n"); %!test %! warning ("off", "quiet", "local"); %! str = evalc ("error ('foo')", "warning ('bar')"); %! assert (str(1:13), "warning: bar\n"); %!error evalc ("switch = 13;") */ DEFUN (__parser_debug_flag__, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} __parser_debug_flag__ () @deftypefnx {} {@var{old_val} =} __parser_debug_flag__ (@var{new_val}) Query or set the internal flag that determines whether Octave's parser prints debug information as it processes an expression. @seealso{__lexer_debug_flag__} @end deftypefn */) { octave_value retval; bool debug_flag = octave_debug; retval = set_internal_variable (debug_flag, args, nargout, "__parser_debug_flag__"); octave_debug = debug_flag; return retval; } DEFMETHOD (__parse_file__, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} __parse_file__ (@var{file}, @var{verbose}) Undocumented internal function. @end deftypefn */) { octave_value retval; int nargin = args.length (); if (nargin < 1 || nargin > 2) print_usage (); std::string file = args(0).xstring_value ("__parse_file__: expecting filename as argument"); std::string full_file = sys::file_ops::tilde_expand (file); full_file = sys::env::make_absolute (full_file); std::string dir_name; std::size_t file_len = file.length (); if ((file_len > 4 && file.substr (file_len-4) == ".oct") || (file_len > 4 && file.substr (file_len-4) == ".mex") || (file_len > 2 && file.substr (file_len-2) == ".m")) { file = sys::env::base_pathname (file); file = file.substr (0, file.find_last_of ('.')); std::size_t pos = file.find_last_of (sys::file_ops::dir_sep_str ()); if (pos != std::string::npos) { dir_name = file.substr (0, pos); file = file.substr (pos+1); } } if (nargin == 2) octave_stdout << "parsing " << full_file << std::endl; octave_value ov_fcn = parse_fcn_file (interp, full_file, file, dir_name, "", "", true, false, false, false); return retval; } OCTAVE_NAMESPACE_END