LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/main - interactive_shell.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 75 107 70.1 %
Date: 2025-01-21 14:34:39 Functions: 3 3 100.0 %
Branches: 54 106 50.9 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * Top contributors (to current version):
       3                 :            :  *   Morgan Deters, Andrew Reynolds, Christopher L. Conway
       4                 :            :  *
       5                 :            :  * This file is part of the cvc5 project.
       6                 :            :  *
       7                 :            :  * Copyright (c) 2009-2024 by the authors listed in the file AUTHORS
       8                 :            :  * in the top-level source directory and their institutional affiliations.
       9                 :            :  * All rights reserved.  See the file COPYING in the top-level source
      10                 :            :  * directory for licensing information.
      11                 :            :  * ****************************************************************************
      12                 :            :  *
      13                 :            :  * Interactive shell for cvc5.
      14                 :            :  *
      15                 :            :  * This file is the implementation for the cvc5 interactive shell.
      16                 :            :  * The shell supports the editline library.
      17                 :            :  */
      18                 :            : #include "main/interactive_shell.h"
      19                 :            : 
      20                 :            : #include <unistd.h>
      21                 :            : 
      22                 :            : #include <algorithm>
      23                 :            : #include <cstdlib>
      24                 :            : #include <cstring>
      25                 :            : #include <iostream>
      26                 :            : #include <optional>
      27                 :            : #include <set>
      28                 :            : #include <string>
      29                 :            : #include <utility>
      30                 :            : #include <vector>
      31                 :            : 
      32                 :            : // This must go before HAVE_LIBEDITLINE.
      33                 :            : #include "base/cvc5config.h"
      34                 :            : 
      35                 :            : #if HAVE_LIBEDITLINE
      36                 :            : #include <editline/readline.h>
      37                 :            : #  if HAVE_EXT_STDIO_FILEBUF_H
      38                 :            : #    include <ext/stdio_filebuf.h>
      39                 :            : #  endif /* HAVE_EXT_STDIO_FILEBUF_H */
      40                 :            : #endif   /* HAVE_LIBEDITLINE */
      41                 :            : 
      42                 :            : #include <cvc5/cvc5.h>
      43                 :            : #include <cvc5/cvc5_parser.h>
      44                 :            : 
      45                 :            : #include "base/check.h"
      46                 :            : #include "base/output.h"
      47                 :            : #include "main/command_executor.h"
      48                 :            : #include "parser/commands.h"
      49                 :            : #include "parser/sym_manager.h"
      50                 :            : #include "theory/logic_info.h"
      51                 :            : 
      52                 :            : using namespace std;
      53                 :            : using namespace cvc5::parser;
      54                 :            : 
      55                 :            : namespace cvc5::internal {
      56                 :            : 
      57                 :            : using namespace language;
      58                 :            : 
      59                 :            : const string InteractiveShell::INPUT_FILENAME = "<shell>";
      60                 :            : 
      61                 :            : #if HAVE_LIBEDITLINE
      62                 :            : 
      63                 :            : #if HAVE_EXT_STDIO_FILEBUF_H
      64                 :            : using __gnu_cxx::stdio_filebuf;
      65                 :            : #endif /* HAVE_EXT_STDIO_FILEBUF_H */
      66                 :            : 
      67                 :            : char** commandCompletion(const char* text, int start, int end);
      68                 :            : char* commandGenerator(const char* text, int state);
      69                 :            : 
      70                 :            : static const std::string smt2_commands[] = {
      71                 :            : #include "main/smt2_tokens.h"
      72                 :            : };/* smt2_commands */
      73                 :            : 
      74                 :            : static const std::string* commandsBegin;
      75                 :            : static const std::string* commandsEnd;
      76                 :            : 
      77                 :            : static set<string> s_declarations;
      78                 :            : 
      79                 :            : #endif /* HAVE_LIBEDITLINE */
      80                 :            : 
      81                 :          7 : InteractiveShell::InteractiveShell(main::CommandExecutor* cexec,
      82                 :            :                                    std::istream& in,
      83                 :            :                                    std::ostream& out,
      84                 :          7 :                                    bool isInteractive)
      85                 :            :     : d_cexec(cexec),
      86                 :          7 :       d_solver(cexec->getSolver()),
      87                 :          7 :       d_symman(cexec->getSymbolManager()->toSymManager()),
      88                 :            :       d_in(in),
      89                 :            :       d_out(out),
      90                 :            :       d_isInteractive(isInteractive),
      91                 :         14 :       d_quit(false)
      92                 :            : {
      93                 :            :   /* Create parser with bogus input. */
      94                 :          7 :   d_parser.reset(
      95                 :          7 :       new cvc5::parser::InputParser(d_solver, cexec->getSymbolManager()));
      96                 :         14 :   std::string langs = d_solver->getOption("input-language");
      97         [ +  + ]:          7 :   if (langs == "LANG_SMTLIB_V2_6")
      98                 :            :   {
      99                 :          6 :     d_lang = modes::InputLanguage::SMT_LIB_2_6;
     100                 :            :   }
     101         [ +  - ]:          1 :   else if (langs == "LANG_SYGUS_V2")
     102                 :            :   {
     103                 :          1 :     d_lang = modes::InputLanguage::SYGUS_2_1;
     104                 :            :   }
     105                 :            :   else
     106                 :            :   {
     107                 :          0 :     throw Exception("internal error: unhandled language " + langs);
     108                 :            :   }
     109                 :            : 
     110                 :            :   // initialize for incremental string input
     111                 :          7 :   d_parser->setStringInput(d_lang, "", INPUT_FILENAME);
     112                 :          7 :   d_usingEditline = false;
     113                 :            : #if HAVE_LIBEDITLINE
     114                 :            :   if (&d_in == &std::cin && isatty(fileno(stdin)))
     115                 :            :   {
     116                 :            :     ::rl_readline_name = const_cast<char*>("cvc5");
     117                 :            : #if EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP
     118                 :            :     ::rl_completion_entry_function = commandGenerator;
     119                 :            : #else /* EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP */
     120                 :            :     ::rl_completion_entry_function = (int (*)(const char*, int)) commandGenerator;
     121                 :            : #endif /* EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP */
     122                 :            :     ::using_history();
     123                 :            : 
     124                 :            :     if (d_lang == modes::InputLanguage::SMT_LIB_2_6)
     125                 :            :     {
     126                 :            :       d_historyFilename = string(getenv("HOME")) + "/.cvc5_history_smtlib2";
     127                 :            :       commandsBegin = smt2_commands;
     128                 :            :       commandsEnd =
     129                 :            :           smt2_commands + sizeof(smt2_commands) / sizeof(*smt2_commands);
     130                 :            :       d_usingEditline = true;
     131                 :            :       int err = ::read_history(d_historyFilename.c_str());
     132                 :            :       ::stifle_history(s_historyLimit);
     133                 :            :       if (d_solver->getOptionInfo("verbosity").intValue() >= 1)
     134                 :            :       {
     135                 :            :         if(err == 0) {
     136                 :            :           d_solver->getDriverOptions().err()
     137                 :            :               << "Read " << ::history_length << " lines of history from "
     138                 :            :               << d_historyFilename << std::endl;
     139                 :            :         } else {
     140                 :            :           d_solver->getDriverOptions().err()
     141                 :            :               << "Could not read history from " << d_historyFilename << ": "
     142                 :            :               << strerror(err) << std::endl;
     143                 :            :         }
     144                 :            :       }
     145                 :            :     }
     146                 :            :   }
     147                 :            : #endif /* HAVE_LIBEDITLINE */
     148                 :          7 : } /* InteractiveShell::InteractiveShell() */
     149                 :            : 
     150                 :          7 : InteractiveShell::~InteractiveShell() {
     151                 :            : #if HAVE_LIBEDITLINE
     152                 :            :   int err = ::write_history(d_historyFilename.c_str());
     153                 :            :   if (d_solver->getOptionInfo("verbosity").intValue() >= 1)
     154                 :            :   {
     155                 :            :     if (err == 0)
     156                 :            :     {
     157                 :            :       d_solver->getDriverOptions().err()
     158                 :            :           << "Wrote " << ::history_length << " lines of history to "
     159                 :            :           << d_historyFilename << std::endl;
     160                 :            :     } else {
     161                 :            :       d_solver->getDriverOptions().err()
     162                 :            :           << "Could not write history to " << d_historyFilename << ": "
     163                 :            :           << strerror(err) << std::endl;
     164                 :            :   }
     165                 :            :   }
     166                 :            : #endif /* HAVE_LIBEDITLINE */
     167                 :          7 : }
     168                 :            : 
     169                 :         17 : bool InteractiveShell::readAndExecCommands()
     170                 :            : {
     171                 :         17 :   char* lineBuf = NULL;
     172                 :         51 :   string line = "";
     173                 :         17 : restart:
     174                 :            : 
     175                 :            :   /* Don't do anything if the input is closed or if we've seen a
     176                 :            :    * QuitCommand. */
     177 [ +  - ][ -  + ]:         17 :   if(d_in.eof() || d_quit) {
                 [ -  + ]
     178         [ -  - ]:          0 :     if (d_isInteractive)
     179                 :            :     {
     180                 :          0 :       d_out << endl;
     181                 :            :     }
     182                 :          0 :     return false;
     183                 :            :   }
     184                 :            : 
     185                 :            :   /* If something's wrong with the input, there's nothing we can do. */
     186         [ -  + ]:         17 :   if( !d_in.good() ) {
     187                 :          0 :     throw ParserException("Interactive input broken.");
     188                 :            :   }
     189                 :            : 
     190                 :            :   /* Prompt the user for input. */
     191         [ +  - ]:         17 :   if (d_usingEditline)
     192                 :            :   {
     193                 :            : #if HAVE_LIBEDITLINE
     194                 :            :     Assert(d_isInteractive);
     195                 :            :     lineBuf = ::readline(line == "" ? "cvc5> " : "... > ");
     196                 :            :     if(lineBuf != NULL && lineBuf[0] != '\0') {
     197                 :            :       ::add_history(lineBuf);
     198                 :            :     }
     199                 :            :     line += lineBuf == NULL ? "" : lineBuf;
     200                 :            :     free(lineBuf);
     201                 :            : #endif /* HAVE_LIBEDITLINE */
     202                 :            :   }
     203                 :            :   else
     204                 :            :   {
     205         [ +  - ]:         17 :     if (d_isInteractive)
     206                 :            :     {
     207         [ +  - ]:         17 :       if (line == "")
     208                 :            :       {
     209                 :         17 :         d_out << "cvc5> " << flush;
     210                 :            :       }
     211                 :            :       else
     212                 :            :       {
     213                 :          0 :         d_out << "... > " << flush;
     214                 :            :       }
     215                 :            :     }
     216                 :            : 
     217                 :            :     /* Read a line */
     218                 :         17 :     stringbuf sb;
     219                 :         17 :     d_in.get(sb);
     220                 :         17 :     line += sb.str();
     221                 :            :   }
     222                 :            : 
     223                 :         34 :   string input = "";
     224                 :            :   while(true) {
     225         [ +  - ]:         34 :     Trace("interactive") << "Input now '" << input << line << "'" << endl
     226                 :         17 :                          << flush;
     227                 :            : 
     228 [ +  + ][ +  + ]:         17 :     Assert(!(d_in.fail() && !d_in.eof()) || line.empty() || !d_isInteractive);
         [ -  + ][ -  - ]
         [ -  + ][ -  - ]
     229                 :            : 
     230                 :            :     /* Check for failure. */
     231 [ +  + ][ +  + ]:         17 :     if(d_in.fail() && !d_in.eof()) {
                 [ +  + ]
     232                 :            :       /* This should only happen if the input line was empty. */
     233 [ -  + ][ -  - ]:          4 :       Assert(line.empty() || !d_isInteractive);
         [ -  + ][ -  - ]
     234                 :          4 :       d_in.clear();
     235                 :            :     }
     236                 :            : 
     237                 :            :     /* Strip trailing whitespace. */
     238                 :         17 :     int n = line.length() - 1;
     239 [ +  + ][ -  + ]:         17 :     while( !line.empty() && isspace(line[n]) ) {
                 [ -  + ]
     240                 :          0 :       line.erase(n,1);
     241                 :          0 :       n--;
     242                 :            :     }
     243                 :            : 
     244                 :            :     /* If we hit EOF, we're done. */
     245         [ +  + ]:         17 :     if ((!d_usingEditline && d_in.eof())
     246 [ +  - ][ -  + ]:         34 :         || (d_usingEditline && lineBuf == NULL))
         [ -  - ][ +  + ]
     247                 :            :     {
     248                 :          7 :       input += line;
     249                 :            : 
     250         [ +  - ]:          7 :       if (input.empty())
     251                 :            :       {
     252                 :            :         /* Nothing left to parse. */
     253         [ +  - ]:          7 :         if (d_isInteractive)
     254                 :            :         {
     255                 :          7 :           d_out << endl;
     256                 :            :         }
     257                 :          7 :         return false;
     258                 :            :       }
     259                 :            : 
     260                 :            :       /* Some input left to parse, but nothing left to read.
     261                 :            :          Jump out of input loop. */
     262                 :          0 :       d_out << endl;
     263                 :          0 :       input = line = "";
     264                 :          0 :       d_in.clear();
     265                 :          0 :       goto restart;
     266                 :            :     }
     267                 :            : 
     268         [ +  - ]:         10 :     if (!d_usingEditline)
     269                 :            :     {
     270                 :            :       /* Extract the newline delimiter from the stream too */
     271                 :         10 :       int c CVC5_UNUSED = d_in.get();
     272 [ -  + ][ -  + ]:         10 :       Assert(c == '\n');
                 [ -  - ]
     273         [ +  - ]:         20 :       Trace("interactive") << "Next char is '" << (char)c << "'" << endl
     274                 :         10 :                            << flush;
     275                 :            :     }
     276                 :            : 
     277                 :         10 :     input += line;
     278                 :            : 
     279                 :            :     /* If the last char was a backslash, continue on the next line. */
     280                 :         10 :     n = input.length() - 1;
     281 [ +  + ][ -  + ]:         10 :     if( !line.empty() && input[n] == '\\' ) {
                 [ -  + ]
     282                 :          0 :       input[n] = '\n';
     283         [ -  - ]:          0 :       if (d_usingEditline)
     284                 :            :       {
     285                 :            : #if HAVE_LIBEDITLINE
     286                 :            :         lineBuf = ::readline("... > ");
     287                 :            :         if(lineBuf != NULL && lineBuf[0] != '\0') {
     288                 :            :           ::add_history(lineBuf);
     289                 :            :         }
     290                 :            :         line = lineBuf == NULL ? "" : lineBuf;
     291                 :            :         free(lineBuf);
     292                 :            : #endif /* HAVE_LIBEDITLINE */
     293                 :            :       }
     294                 :            :       else
     295                 :            :       {
     296         [ -  - ]:          0 :         if (d_isInteractive)
     297                 :            :         {
     298                 :          0 :           d_out << "... > " << flush;
     299                 :            :         }
     300                 :            : 
     301                 :            :         /* Read a line */
     302                 :          0 :         stringbuf sb;
     303                 :          0 :         d_in.get(sb);
     304                 :          0 :         line = sb.str();
     305                 :            :       }
     306                 :            :     } else {
     307                 :            :       /* No continuation, we're done. */
     308         [ +  - ]:         10 :       Trace("interactive") << "Leaving input loop." << endl << flush;
     309                 :         10 :       break;
     310                 :            :     }
     311                 :          0 :   }
     312                 :            : 
     313                 :            :   // set new string input, without a new parser
     314                 :         10 :   d_parser->setStringInputInternal(input, INPUT_FILENAME);
     315                 :            : 
     316                 :            :   /* There may be more than one command in the input. Build up a
     317                 :            :      sequence. */
     318                 :         10 :   std::vector<Command> cmdSeq;
     319                 :         10 :   Command cmdp;
     320                 :            :   // remember the scope level of the symbol manager, in case we hit an end of
     321                 :            :   // line (when catching ParserEndOfFileException).
     322                 :         10 :   size_t lastScopeLevel = d_symman->scopeLevel();
     323                 :            : 
     324                 :            :   try
     325                 :            :   {
     326                 :            :     while (true)
     327                 :            :     {
     328                 :         16 :       cmdp = d_parser->nextCommand();
     329         [ +  + ]:         15 :       if (cmdp.isNull())
     330                 :            :       {
     331                 :          9 :         break;
     332                 :            :       }
     333                 :          6 :       Cmd* cmd = cmdp.d_cmd.get();
     334                 :            :       // execute the command immediately
     335                 :          6 :       d_cexec->doCommand(&cmdp);
     336         [ -  + ]:          6 :       if (cmd->interrupted())
     337                 :            :       {
     338                 :          0 :         d_quit = true;
     339                 :          0 :         return false;
     340                 :            :       }
     341                 :          6 :       cmdSeq.emplace_back(std::move(cmdp));
     342 [ +  - ][ -  + ]:          6 :       if (dynamic_cast<QuitCommand*>(cmd) != NULL)
     343                 :            :       {
     344                 :          0 :         d_quit = true;
     345                 :          0 :         break;
     346                 :            :       }
     347                 :            :       else
     348                 :            :       {
     349                 :            : #if HAVE_LIBEDITLINE
     350                 :            :         if (dynamic_cast<DeclareFunctionCommand*>(cmd) != NULL)
     351                 :            :         {
     352                 :            :           s_declarations.insert(
     353                 :            :               dynamic_cast<DeclareFunctionCommand*>(cmd)->getSymbol());
     354                 :            :         }
     355                 :            :         else if (dynamic_cast<DefineFunctionCommand*>(cmd) != NULL)
     356                 :            :         {
     357                 :            :           s_declarations.insert(
     358                 :            :               dynamic_cast<DefineFunctionCommand*>(cmd)->getSymbol());
     359                 :            :         }
     360                 :            :         else if (dynamic_cast<DeclareSortCommand*>(cmd) != NULL)
     361                 :            :         {
     362                 :            :           s_declarations.insert(
     363                 :            :               dynamic_cast<DeclareSortCommand*>(cmd)->getSymbol());
     364                 :            :         }
     365                 :            :         else if (dynamic_cast<DefineSortCommand*>(cmd) != NULL)
     366                 :            :         {
     367                 :            :           s_declarations.insert(
     368                 :            :               dynamic_cast<DefineSortCommand*>(cmd)->getSymbol());
     369                 :            :         }
     370                 :            : #endif /* HAVE_LIBEDITLINE */
     371                 :            :       }
     372                 :          6 :       lastScopeLevel = d_symman->scopeLevel();
     373                 :          6 :     }
     374                 :            :   }
     375                 :          0 :   catch (ParserEndOfFileException& pe)
     376                 :            :   {
     377                 :            :     // pop back to the scope we were at prior to reading the last command
     378         [ -  - ]:          0 :     while (d_symman->scopeLevel() > lastScopeLevel)
     379                 :            :     {
     380                 :          0 :       d_symman->popScope();
     381                 :            :     }
     382                 :          0 :     line += "\n";
     383                 :          0 :     goto restart;
     384                 :            :   }
     385         [ +  - ]:          2 :   catch (ParserException& pe)
     386                 :            :   {
     387         [ -  + ]:          1 :     if (d_solver->getOption("output-language") == "LANG_SMTLIB_V2_6")
     388                 :            :     {
     389                 :          0 :       d_out << "(error \"" << pe << "\")" << endl;
     390                 :            :     }
     391                 :            :     else
     392                 :            :     {
     393                 :          1 :       d_out << pe << endl;
     394                 :            :     }
     395                 :            :     // if not interactive, we quit when we encounter a parse error
     396         [ -  + ]:          1 :     if (!d_isInteractive)
     397                 :            :     {
     398                 :          0 :       d_quit = true;
     399                 :          0 :       return false;
     400                 :            :     }
     401                 :            :     // We can't really clear out the sequence and abort the current line,
     402                 :            :     // because the parse error might be for the second command on the
     403                 :            :     // line.  The first ones haven't yet been executed by the SolverEngine,
     404                 :            :     // but the parser state has already made the variables and the mappings
     405                 :            :     // in the symbol table.  So unfortunately, either we exit cvc5 entirely,
     406                 :            :     // or we commit to the current line up to the command with the parse
     407                 :            :     // error.
     408                 :            :     //
     409                 :            :     // FIXME: does that mean e.g. that a pushed LET scope might not yet have
     410                 :            :     // been popped?!
     411                 :            :     //
     412                 :            :     // delete cmd_seq;
     413                 :            :     // cmd_seq = new CommandSequence();
     414                 :            :   }
     415                 :            : 
     416                 :         10 :   return true;
     417                 :            : }/* InteractiveShell::readCommand() */
     418                 :            : 
     419                 :            : #if HAVE_LIBEDITLINE
     420                 :            : 
     421                 :            : char** commandCompletion(const char* text, int start, int end) {
     422                 :            :   Trace("rl") << "text: " << text << endl;
     423                 :            :   Trace("rl") << "start: " << start << " end: " << end << endl;
     424                 :            :   return rl_completion_matches(text, commandGenerator);
     425                 :            : }
     426                 :            : 
     427                 :            : // Our peculiar versions of "less than" for strings
     428                 :            : struct StringPrefix1Less {
     429                 :            :   bool operator()(const std::string& s1, const std::string& s2) {
     430                 :            :     size_t l1 = s1.length(), l2 = s2.length();
     431                 :            :     size_t l = l1 <= l2 ? l1 : l2;
     432                 :            :     return s1.compare(0, l1, s2, 0, l) < 0;
     433                 :            :   }
     434                 :            : };/* struct StringPrefix1Less */
     435                 :            : struct StringPrefix2Less {
     436                 :            :   bool operator()(const std::string& s1, const std::string& s2) {
     437                 :            :     size_t l1 = s1.length(), l2 = s2.length();
     438                 :            :     size_t l = l1 <= l2 ? l1 : l2;
     439                 :            :     return s1.compare(0, l, s2, 0, l2) < 0;
     440                 :            :   }
     441                 :            : };/* struct StringPrefix2Less */
     442                 :            : 
     443                 :            : char* commandGenerator(const char* text, int state) {
     444                 :            :   static thread_local const std::string* rlCommand;
     445                 :            :   static thread_local set<string>::const_iterator* rlDeclaration;
     446                 :            : 
     447                 :            :   const std::string* i = lower_bound(commandsBegin, commandsEnd, text, StringPrefix2Less());
     448                 :            :   const std::string* j = upper_bound(commandsBegin, commandsEnd, text, StringPrefix1Less());
     449                 :            : 
     450                 :            :   set<string>::const_iterator ii = lower_bound(s_declarations.begin(), s_declarations.end(), text, StringPrefix2Less());
     451                 :            :   set<string>::const_iterator jj = upper_bound(s_declarations.begin(), s_declarations.end(), text, StringPrefix1Less());
     452                 :            : 
     453                 :            :   if(rlDeclaration == NULL) {
     454                 :            :     rlDeclaration = new set<string>::const_iterator();
     455                 :            :   }
     456                 :            : 
     457                 :            :   if(state == 0) {
     458                 :            :     rlCommand = i;
     459                 :            :     *rlDeclaration = ii;
     460                 :            :   }
     461                 :            : 
     462                 :            :   if(rlCommand != j) {
     463                 :            :     return strdup((*rlCommand++).c_str());
     464                 :            :   }
     465                 :            : 
     466                 :            :   return *rlDeclaration == jj ? NULL : strdup((*(*rlDeclaration)++).c_str());
     467                 :            : }
     468                 :            : 
     469                 :            : #endif /* HAVE_LIBEDITLINE */
     470                 :            : 
     471                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14