GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/main/interactive_shell.cpp Lines: 56 84 66.7 %
Date: 2021-11-07 Branches: 102 289 35.3 %

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