1 /******************************************************************************
2 * Top contributors (to current version):
3 * Morgan Deters, Christopher L. Conway, Andrew V. Jones
5 * This file is part of the cvc5 project.
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 * ****************************************************************************
13 * Interactive shell for cvc5.
15 * This file is the implementation for the cvc5 interactive shell.
16 * The shell supports the editline library.
18 #include "main/interactive_shell.h"
29 // This must go before HAVE_LIBEDITLINE.
30 #include "base/cvc5config.h"
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 */
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/base_options.h"
44 #include "options/language.h"
45 #include "options/main_options.h"
46 #include "options/options.h"
47 #include "options/parser_options.h"
48 #include "parser/input.h"
49 #include "parser/parser.h"
50 #include "parser/parser_builder.h"
51 #include "smt/command.h"
52 #include "theory/logic_info.h"
58 using namespace parser
;
59 using namespace language
;
61 const string
InteractiveShell::INPUT_FILENAME
= "<shell>";
65 #if HAVE_EXT_STDIO_FILEBUF_H
66 using __gnu_cxx::stdio_filebuf
;
67 #endif /* HAVE_EXT_STDIO_FILEBUF_H */
69 char** commandCompletion(const char* text
, int start
, int end
);
70 char* commandGenerator(const char* text
, int state
);
72 static const std::string cvc_commands
[] = {
73 #include "main/cvc_tokens.h"
76 static const std::string smt2_commands
[] = {
77 #include "main/smt2_tokens.h"
80 static const std::string tptp_commands
[] = {
81 #include "main/tptp_tokens.h"
84 static const std::string
* commandsBegin
;
85 static const std::string
* commandsEnd
;
87 static set
<string
> s_declarations
;
89 #endif /* HAVE_LIBEDITLINE */
91 InteractiveShell::InteractiveShell(api::Solver
* solver
, SymbolManager
* sm
)
92 : d_options(solver
->getOptions()),
93 d_in(*d_options
.base
.in
),
94 d_out(*d_options
.base
.out
),
97 ParserBuilder
parserBuilder(solver
, sm
, d_options
);
98 /* Create parser with bogus input. */
99 d_parser
= parserBuilder
.build();
100 if (d_options
.parser
.forceLogicStringWasSetByUser
)
102 LogicInfo
tmp(d_options
.parser
.forceLogicString
);
103 d_parser
->forceLogic(tmp
.getLogicString());
108 ::rl_readline_name
= const_cast<char*>("cvc5");
109 #if EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP
110 ::rl_completion_entry_function
= commandGenerator
;
111 #else /* EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP */
112 ::rl_completion_entry_function
= (int (*)(const char*, int)) commandGenerator
;
113 #endif /* EDITLINE_COMPENTRY_FUNC_RETURNS_CHARP */
116 OutputLanguage lang
=
117 toOutputLanguage(d_options
.base
.inputLanguage
);
119 case output::LANG_CVC
:
120 d_historyFilename
= string(getenv("HOME")) + "/.cvc5_history";
121 commandsBegin
= cvc_commands
;
123 cvc_commands
+ sizeof(cvc_commands
) / sizeof(*cvc_commands
);
125 case output::LANG_TPTP
:
126 d_historyFilename
= string(getenv("HOME")) + "/.cvc5_history_tptp";
127 commandsBegin
= tptp_commands
;
129 tptp_commands
+ sizeof(tptp_commands
) / sizeof(*tptp_commands
);
132 if (language::isOutputLang_smt2(lang
))
134 d_historyFilename
= string(getenv("HOME")) + "/.cvc5_history_smtlib2";
135 commandsBegin
= smt2_commands
;
137 smt2_commands
+ sizeof(smt2_commands
) / sizeof(*smt2_commands
);
141 std::stringstream ss
;
142 ss
<< "internal error: unhandled language " << lang
;
143 throw Exception(ss
.str());
146 d_usingEditline
= true;
147 int err
= ::read_history(d_historyFilename
.c_str());
148 ::stifle_history(s_historyLimit
);
151 Notice() << "Read " << ::history_length
<< " lines of history from "
152 << d_historyFilename
<< std::endl
;
154 Notice() << "Could not read history from " << d_historyFilename
155 << ": " << strerror(err
) << std::endl
;
159 d_usingEditline
= false;
161 #else /* HAVE_LIBEDITLINE */
162 d_usingEditline
= false;
163 #endif /* HAVE_LIBEDITLINE */
164 }/* InteractiveShell::InteractiveShell() */
166 InteractiveShell::~InteractiveShell() {
168 int err
= ::write_history(d_historyFilename
.c_str());
170 Notice() << "Wrote " << ::history_length
<< " lines of history to "
171 << d_historyFilename
<< std::endl
;
173 Notice() << "Could not write history to " << d_historyFilename
174 << ": " << strerror(err
) << std::endl
;
176 #endif /* HAVE_LIBEDITLINE */
180 Command
* InteractiveShell::readCommand()
182 char* lineBuf
= NULL
;
187 /* Don't do anything if the input is closed or if we've seen a
189 if(d_in
.eof() || d_quit
) {
194 /* If something's wrong with the input, there's nothing we can do. */
196 throw ParserException("Interactive input broken.");
199 /* Prompt the user for input. */
203 lineBuf
= ::readline(d_options
.driver
.interactivePrompt
204 ? (line
== "" ? "cvc5> " : "... > ")
206 if(lineBuf
!= NULL
&& lineBuf
[0] != '\0') {
207 ::add_history(lineBuf
);
209 line
+= lineBuf
== NULL
? "" : lineBuf
;
211 #endif /* HAVE_LIBEDITLINE */
215 if (d_options
.driver
.interactivePrompt
)
218 d_out
<< "cvc5> " << flush
;
220 d_out
<< "... > " << flush
;
232 Debug("interactive") << "Input now '" << input
<< line
<< "'" << endl
235 Assert(!(d_in
.fail() && !d_in
.eof()) || line
.empty());
237 /* Check for failure. */
238 if(d_in
.fail() && !d_in
.eof()) {
239 /* This should only happen if the input line was empty. */
240 Assert(line
.empty());
244 /* Strip trailing whitespace. */
245 int n
= line
.length() - 1;
246 while( !line
.empty() && isspace(line
[n
]) ) {
251 /* If we hit EOF, we're done. */
252 if ((!d_usingEditline
&& d_in
.eof())
253 || (d_usingEditline
&& lineBuf
== NULL
))
258 /* Nothing left to parse. */
263 /* Some input left to parse, but nothing left to read.
264 Jump out of input loop. */
271 if (!d_usingEditline
)
273 /* Extract the newline delimiter from the stream too */
274 int c CVC5_UNUSED
= d_in
.get();
276 Debug("interactive") << "Next char is '" << (char)c
<< "'" << endl
282 /* If the last char was a backslash, continue on the next line. */
283 n
= input
.length() - 1;
284 if( !line
.empty() && input
[n
] == '\\' ) {
289 lineBuf
= ::readline(d_options
.driver
.interactivePrompt
? "... > "
291 if(lineBuf
!= NULL
&& lineBuf
[0] != '\0') {
292 ::add_history(lineBuf
);
294 line
= lineBuf
== NULL
? "" : lineBuf
;
296 #endif /* HAVE_LIBEDITLINE */
300 if (d_options
.driver
.interactivePrompt
)
302 d_out
<< "... > " << flush
;
311 /* No continuation, we're done. */
312 Debug("interactive") << "Leaving input loop." << endl
<< flush
;
317 d_parser
->setInput(Input::newStringInput(
318 d_options
.base
.inputLanguage
, input
, INPUT_FILENAME
));
320 /* There may be more than one command in the input. Build up a
322 CommandSequence
*cmd_seq
= new CommandSequence();
327 while ((cmd
= d_parser
->nextCommand()))
329 cmd_seq
->addCommand(cmd
);
330 if (dynamic_cast<QuitCommand
*>(cmd
) != NULL
)
338 if (dynamic_cast<DeclareFunctionCommand
*>(cmd
) != NULL
)
340 s_declarations
.insert(
341 dynamic_cast<DeclareFunctionCommand
*>(cmd
)->getSymbol());
343 else if (dynamic_cast<DefineFunctionCommand
*>(cmd
) != NULL
)
345 s_declarations
.insert(
346 dynamic_cast<DefineFunctionCommand
*>(cmd
)->getSymbol());
348 else if (dynamic_cast<DeclareSortCommand
*>(cmd
) != NULL
)
350 s_declarations
.insert(
351 dynamic_cast<DeclareSortCommand
*>(cmd
)->getSymbol());
353 else if (dynamic_cast<DefineSortCommand
*>(cmd
) != NULL
)
355 s_declarations
.insert(
356 dynamic_cast<DefineSortCommand
*>(cmd
)->getSymbol());
358 #endif /* HAVE_LIBEDITLINE */
362 catch (ParserEndOfFileException
& pe
)
367 catch (ParserException
& pe
)
369 if (language::isOutputLang_smt2(d_options
.base
.outputLanguage
))
371 d_out
<< "(error \"" << pe
<< "\")" << endl
;
377 // We can't really clear out the sequence and abort the current line,
378 // because the parse error might be for the second command on the
379 // line. The first ones haven't yet been executed by the SmtEngine,
380 // but the parser state has already made the variables and the mappings
381 // in the symbol table. So unfortunately, either we exit cvc5 entirely,
382 // or we commit to the current line up to the command with the parse
385 // FIXME: does that mean e.g. that a pushed LET scope might not yet have
389 // cmd_seq = new CommandSequence();
393 }/* InteractiveShell::readCommand() */
397 char** commandCompletion(const char* text
, int start
, int end
) {
398 Debug("rl") << "text: " << text
<< endl
;
399 Debug("rl") << "start: " << start
<< " end: " << end
<< endl
;
400 return rl_completion_matches(text
, commandGenerator
);
403 // Our peculiar versions of "less than" for strings
404 struct StringPrefix1Less
{
405 bool operator()(const std::string
& s1
, const std::string
& s2
) {
406 size_t l1
= s1
.length(), l2
= s2
.length();
407 size_t l
= l1
<= l2
? l1
: l2
;
408 return s1
.compare(0, l1
, s2
, 0, l
) < 0;
410 };/* struct StringPrefix1Less */
411 struct StringPrefix2Less
{
412 bool operator()(const std::string
& s1
, const std::string
& s2
) {
413 size_t l1
= s1
.length(), l2
= s2
.length();
414 size_t l
= l1
<= l2
? l1
: l2
;
415 return s1
.compare(0, l
, s2
, 0, l2
) < 0;
417 };/* struct StringPrefix2Less */
419 char* commandGenerator(const char* text
, int state
) {
420 static thread_local
const std::string
* rlCommand
;
421 static thread_local set
<string
>::const_iterator
* rlDeclaration
;
423 const std::string
* i
= lower_bound(commandsBegin
, commandsEnd
, text
, StringPrefix2Less());
424 const std::string
* j
= upper_bound(commandsBegin
, commandsEnd
, text
, StringPrefix1Less());
426 set
<string
>::const_iterator ii
= lower_bound(s_declarations
.begin(), s_declarations
.end(), text
, StringPrefix2Less());
427 set
<string
>::const_iterator jj
= upper_bound(s_declarations
.begin(), s_declarations
.end(), text
, StringPrefix1Less());
429 if(rlDeclaration
== NULL
) {
430 rlDeclaration
= new set
<string
>::const_iterator();
439 return strdup((*rlCommand
++).c_str());
442 return *rlDeclaration
== jj
? NULL
: strdup((*(*rlDeclaration
)++).c_str());
445 #endif /* HAVE_LIBEDITLINE */