GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/main/interactive_shell.cpp Lines: 59 88 67.0 %
Date: 2021-05-24 Branches: 103 295 34.9 %

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