changeset 29857:0b01806bb663

fix command syntax parsing error (bug #60882) * lex.h, lex.ll (lexical_feedback::maybe_mark_previous_token_as_variable): Delete function and all uses. (lexical_feedback::m_pending_local_variables): Now a list of sets. Change all uses. (lexical_feedback::init): Insert empty set as first element of m_pending_local_variables. (lexical_feedback::reset): Preserve first element of m_pending_local_variables. (lexical_feedback::mark_as_variable): New function. (lexical_feedback::is_variable): New function. (base_lexer::is_variable): Delete. * oct-parse.yy: Anywhere a symbol scope is pushed to or popped from the symbol table context, also push or pop a set of pending local variables. Mark symbols that appear on the LHS of '=' operators as pending variables for the current scope. * test/bug-60882/bug-60882.tst, test/bug-60882/bug_60882.m, test/bug-60882/module.mk: New files. * test/module.mk: Update.
author John W. Eaton <jwe@octave.org>
date Tue, 06 Jul 2021 23:16:14 -0400
parents 56b3e2af0298
children 6dc298d3261c
files libinterp/parse-tree/lex.h libinterp/parse-tree/lex.ll libinterp/parse-tree/oct-parse.yy test/bug-60882/bug-60882.tst test/bug-60882/bug_60882.m test/bug-60882/module.mk test/module.mk
diffstat 7 files changed, 106 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/parse-tree/lex.h	Tue Jul 06 11:12:20 2021 -0700
+++ b/libinterp/parse-tree/lex.h	Tue Jul 06 23:16:14 2021 -0400
@@ -347,11 +347,11 @@
 
     bool previous_token_may_be_command (void) const;
 
-    void maybe_mark_previous_token_as_variable (void);
-
     void mark_as_variable (const std::string& nm);
     void mark_as_variables (const std::list<std::string>& lst);
 
+    bool is_variable (const std::string& nm) const;
+
     interpreter& m_interpreter;
 
     // true means that we have encountered eof on the input stream.
@@ -515,8 +515,10 @@
     // current_function_level > 0
     std::stack<bool> m_parsed_function_name;
 
-    // set of identifiers that might be local variable names.
-    std::set<std::string> m_pending_local_variables;
+    // A list of sets of identifiers that might be local variable names.
+    // The front of the list corresponds to the current scope.  The next
+    // element is for the parent scope, etc.
+    std::list<std::set<std::string>> m_pending_local_variables;
 
     // Track current symbol table scope and context.
     symbol_table_context m_symtab_context;
@@ -657,8 +659,6 @@
 
     bool inside_any_object_index (void);
 
-    bool is_variable (const std::string& name);
-
     int make_keyword_token (const std::string& s);
 
     bool fq_identifier_contains_keyword (const std::string& s);
--- a/libinterp/parse-tree/lex.ll	Tue Jul 06 11:12:20 2021 -0700
+++ b/libinterp/parse-tree/lex.ll	Tue Jul 06 23:16:14 2021 -0400
@@ -1817,8 +1817,6 @@
 "=" {
     curr_lexer->lexer_debug ("=");
 
-    curr_lexer->maybe_mark_previous_token_as_variable ();
-
     return curr_lexer->handle_op ('=');
   }
 
@@ -2223,6 +2221,12 @@
     // The closest paren, brace, or bracket nesting is not an object
     // index.
     m_looking_at_object_index.push_front (false);
+
+    // Provide an initial set to store variables at the top-level.
+    // Don't clear this one when resetting lexical_feedback state.
+    // It should persist since the top-level scope does.  Hmm maybe
+    // we should just use the symbol scope object for this job?
+    m_pending_local_variables.push_front (std::set<std::string> ());
   }
 
   void
@@ -2280,7 +2284,9 @@
     while (! m_parsed_function_name.empty ())
       m_parsed_function_name.pop ();
 
-    m_pending_local_variables.clear ();
+    while (m_pending_local_variables.size () > 1)
+      m_pending_local_variables.pop_front ();
+
     m_symtab_context.clear ();
     m_nesting_level.reset ();
     m_tokens.clear ();
@@ -2353,19 +2359,34 @@
   }
 
   void
-  lexical_feedback::maybe_mark_previous_token_as_variable (void)
+  lexical_feedback::mark_as_variable (const std::string& nm)
   {
-    token *tok = m_tokens.front ();
-
-    if (tok && tok->isstring ())
-      m_pending_local_variables.insert (tok->text ());
+    auto& vars = m_pending_local_variables.front ();
+    vars.insert (nm);
   }
 
   void
   lexical_feedback::mark_as_variables (const std::list<std::string>& lst)
   {
-    for (const auto& var : lst)
-      m_pending_local_variables.insert (var);
+    auto& vars = m_pending_local_variables.front ();
+    for (const auto& nm : lst)
+      vars.insert (nm);
+  }
+
+  bool
+  lexical_feedback::is_variable (const std::string& nm) const
+  {
+    if (m_interpreter.at_top_level () && m_interpreter.is_variable (nm))
+      return true;
+
+    // Search current scope, then parents.
+    for (const auto& vars : m_pending_local_variables)
+      {
+        if (vars.find (nm) != vars.end ())
+          return true;
+      }
+
+    return false;
   }
 }
 
@@ -2656,15 +2677,6 @@
     return retval;
   }
 
-  bool
-  base_lexer::is_variable (const std::string& name)
-  {
-    return ((m_interpreter.at_top_level ()
-             && m_interpreter.is_variable (name))
-            || (m_pending_local_variables.find (name)
-                != m_pending_local_variables.end ()));
-  }
-
   int
   base_lexer::make_keyword_token (const std::string& s)
   {
--- a/libinterp/parse-tree/oct-parse.yy	Tue Jul 06 11:12:20 2021 -0700
+++ b/libinterp/parse-tree/oct-parse.yy	Tue Jul 06 23:16:14 2021 -0400
@@ -1451,7 +1451,8 @@
                     if (lexer.m_looking_at_function_handle)
                       {
                         // Will get a real name later.
-                        lexer.m_symtab_context.push (octave::symbol_scope ("parser:param_lsit_beg"));
+                        lexer.m_symtab_context.push (octave::symbol_scope ("parser:param_list_beg"));
+                        lexer.m_pending_local_variables.push_front (std::set<std::string> ());
                         lexer.m_looking_at_function_handle--;
                         lexer.m_looking_at_anon_fcn_args = true;
                       }
@@ -1607,6 +1608,7 @@
                     // 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"));
+                    lexer.m_pending_local_variables.push_front (std::set<std::string> ());
                   }
                 ;
 
@@ -1628,6 +1630,7 @@
 
                         // Unused symbol table context.
                         lexer.m_symtab_context.pop ();
+                        lexer.m_pending_local_variables.pop_front ();
 
                         delete $3;
                       }
@@ -1650,6 +1653,7 @@
 
                     // Unused symbol table context.
                     lexer.m_symtab_context.pop ();
+                    lexer.m_pending_local_variables.pop_front ();
 
                     parser.finish_classdef_file ($3, $6);
 
@@ -1952,6 +1956,7 @@
 
                     // Create invalid parent scope.
                     lexer.m_symtab_context.push (octave::symbol_scope ());
+                    lexer.m_pending_local_variables.push_front (std::set<std::string> ());
                     lexer.m_parsing_classdef = true;
                     lexer.m_parsing_classdef_decl = true;
                     lexer.m_classdef_element_names_are_keywords = true;
@@ -2824,6 +2829,7 @@
 
     // Will get a real name later.
     m_lexer.m_symtab_context.push (symbol_scope ("parser:push_fcn_symtab"));
+    m_lexer.m_pending_local_variables.push_front (std::set<std::string> ());
     m_function_scopes.push (m_lexer.m_symtab_context.curr_scope ());
 
     if (! m_lexer.m_reading_script_file && m_curr_fcn_depth == 0
@@ -2938,6 +2944,7 @@
     symbol_scope parent_scope = m_lexer.m_symtab_context.parent_scope ();
 
     m_lexer.m_symtab_context.pop ();
+    m_lexer.m_pending_local_variables.pop_front ();
 
     expr->set_print_flag (false);
 
@@ -3428,6 +3435,8 @@
           {
             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);
 
@@ -3444,9 +3453,11 @@
 
                 bison_error ("invalid syntax for parfor statement");
               }
-            else
-              retval = new tree_complex_for_command (lhs, expr, body,
-                                                     lc, tc, l, c);
+
+            m_lexer.mark_as_variables (lhs->variable_names ());
+
+            retval = new tree_complex_for_command (lhs, expr, body,
+                                                   lc, tc, l, c);
           }
       }
     else
@@ -3769,6 +3780,8 @@
 
         delete lhs;
 
+        m_lexer.mark_as_variable (tmp->name ());
+
         return new tree_simple_assignment (tmp, rhs, false, l, c, t);
       }
     else
@@ -3789,6 +3802,8 @@
               }
           }
 
+        m_lexer.mark_as_variables (names);
+
         return new tree_multi_assignment (lhs, rhs, false, l, c);
       }
   }
@@ -3816,6 +3831,7 @@
                                 cmds, m_lexer.m_help_text);
 
     m_lexer.m_symtab_context.pop ();
+    m_lexer.m_pending_local_variables.pop_front ();
     m_lexer.m_help_text = "";
 
     sys::time now;
@@ -4216,6 +4232,7 @@
   base_parser::recover_from_parsing_function (void)
   {
     m_lexer.m_symtab_context.pop ();
+    m_lexer.m_pending_local_variables.pop_front ();
 
     if (m_lexer.m_reading_fcn_file && m_curr_fcn_depth == 0
         && ! m_parsing_subfunctions)
@@ -4251,6 +4268,7 @@
     tree_classdef *retval = nullptr;
 
     m_lexer.m_symtab_context.pop ();
+    m_lexer.m_pending_local_variables.pop_front ();
 
     std::string cls_name = id->name ();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug-60882/bug-60882.tst	Tue Jul 06 23:16:14 2021 -0400
@@ -0,0 +1,28 @@
+########################################################################
+##
+## Copyright (C) 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/>.
+##
+########################################################################
+
+## bug #60882: error parsing command syntax
+
+%!assert (bug_60882 (), 42)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug-60882/bug_60882.m	Tue Jul 06 23:16:14 2021 -0400
@@ -0,0 +1,13 @@
+function retval = bug_60882 ()
+
+  job.foobar = {};
+
+  foobar off
+
+  retval = 42;
+
+endfunction
+
+function foobar (opt)
+  assert (opt, 'off');
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug-60882/module.mk	Tue Jul 06 23:16:14 2021 -0400
@@ -0,0 +1,5 @@
+bug_60882_TEST_FILES = \
+  %reldir%/bug-60882.tst \
+  %reldir%/bug_60882.m
+
+TEST_FILES += $(bug_60882_TEST_FILES)
--- a/test/module.mk	Tue Jul 06 11:12:20 2021 -0700
+++ b/test/module.mk	Tue Jul 06 23:16:14 2021 -0400
@@ -95,6 +95,7 @@
 include %reldir%/bug-59704/module.mk
 include %reldir%/bug-59937/module.mk
 include %reldir%/bug-60237/module.mk
+include %reldir%/bug-60882/module.mk
 include %reldir%/class-concat/module.mk
 include %reldir%/classdef/module.mk
 include %reldir%/classdef-multiple-inheritance/module.mk