GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/parser/tptp/tptp.cpp Lines: 211 261 80.8 %
Date: 2021-05-22 Branches: 296 804 36.8 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Francois Bobot, Haniel Barbosa
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
 * Definition of TPTP parser.
14
 */
15
16
// Do not #include "parser/antlr_input.h" directly. Rely on the header.
17
#include "parser/tptp/tptp.h"
18
19
#include <algorithm>
20
#include <set>
21
22
#include "api/cpp/cvc5.h"
23
#include "base/check.h"
24
#include "options/options.h"
25
#include "parser/parser.h"
26
#include "smt/command.h"
27
28
// ANTLR defines these, which is really bad!
29
#undef true
30
#undef false
31
32
namespace cvc5 {
33
namespace parser {
34
35
43
Tptp::Tptp(api::Solver* solver,
36
           SymbolManager* sm,
37
           bool strictMode,
38
43
           bool parseOnly)
39
43
    : Parser(solver, sm, strictMode, parseOnly), d_cnf(false), d_fof(false)
40
{
41
43
  addTheory(Tptp::THEORY_CORE);
42
43
  /* Try to find TPTP dir */
44
  // From tptp4x FileUtilities
45
  //----Try the TPTP directory, and TPTP variations
46
43
  char* home = getenv("TPTP");
47
43
  if(home == NULL) {
48
43
     home = getenv("TPTP_HOME");
49
// //----If no TPTP_HOME, try the tptp user (aaargh)
50
//         if(home == NULL && (PasswdEntry = getpwnam("tptp")) != NULL) {
51
//            home = PasswdEntry->pw_dir;
52
//         }
53
//----Now look in the TPTP directory from there
54
43
    if(home != NULL) {
55
      d_tptpDir = home;
56
      d_tptpDir.append("/TPTP/");
57
    }
58
  } else {
59
    d_tptpDir = home;
60
    //add trailing "/"
61
    if(d_tptpDir[d_tptpDir.size() - 1] != '/') {
62
      d_tptpDir.append("/");
63
    }
64
  }
65
43
  d_hasConjecture = false;
66
43
}
67
68
129
Tptp::~Tptp() {
69
50
  for( unsigned i=0; i<d_in_created.size(); i++ ){
70
7
    d_in_created[i]->free(d_in_created[i]);
71
  }
72
86
}
73
74
43
void Tptp::addTheory(Theory theory) {
75
43
  switch(theory) {
76
43
  case THEORY_CORE:
77
    //TPTP (CNF and FOF) is unsorted so we define this common type
78
    {
79
86
      std::string d_unsorted_name = "$$unsorted";
80
43
      d_unsorted = d_solver->mkUninterpretedSort(d_unsorted_name);
81
86
      preemptCommand(new DeclareSortCommand(d_unsorted_name, 0, d_unsorted));
82
    }
83
    // propositionnal
84
43
    defineType("Bool", d_solver->getBooleanSort());
85
43
    defineVar("$true", d_solver->mkTrue());
86
43
    defineVar("$false", d_solver->mkFalse());
87
43
    addOperator(api::AND);
88
43
    addOperator(api::EQUAL);
89
43
    addOperator(api::IMPLIES);
90
    // addOperator(api::ITE); //only for tff thf
91
43
    addOperator(api::NOT);
92
43
    addOperator(api::OR);
93
43
    addOperator(api::XOR);
94
43
    addOperator(api::APPLY_UF);
95
    //Add quantifiers?
96
43
    break;
97
98
  default:
99
    std::stringstream ss;
100
    ss << "internal error: Tptp::addTheory(): unhandled theory " << theory;
101
    throw ParserException(ss.str());
102
  }
103
43
}
104
105
106
/* The include are managed in the lexer but called in the parser */
107
// Inspired by http://www.antlr3.org/api/C/interop.html
108
109
7
bool newInputStream(std::string fileName, pANTLR3_LEXER lexer, std::vector< pANTLR3_INPUT_STREAM >& inc ) {
110
7
  Debug("parser") << "Including " << fileName << std::endl;
111
  // Create a new input stream and take advantage of built in stream stacking
112
  // in C target runtime.
113
  //
114
  pANTLR3_INPUT_STREAM    in;
115
#ifdef CVC5_ANTLR3_OLD_INPUT_STREAM
116
  in = antlr3AsciiFileStreamNew((pANTLR3_UINT8) fileName.c_str());
117
#else  /* CVC5_ANTLR3_OLD_INPUT_STREAM */
118
7
  in = antlr3FileStreamNew((pANTLR3_UINT8) fileName.c_str(), ANTLR3_ENC_8BIT);
119
#endif /* CVC5_ANTLR3_OLD_INPUT_STREAM */
120
7
  if(in == NULL) {
121
    Debug("parser") << "Can't open " << fileName << std::endl;
122
    return false;
123
  }
124
  // Same thing as the predefined PUSHSTREAM(in);
125
7
  lexer->pushCharStream(lexer,in);
126
  // restart it
127
  //lexer->rec->state->tokenStartCharIndex  = -10;
128
  //lexer->emit(lexer);
129
130
  // Note that the input stream is not closed when it EOFs, I don't bother
131
  // to do it here, but it is up to you to track streams created like this
132
  // and destroy them when the whole parse session is complete. Remember that you
133
  // don't want to do this until all tokens have been manipulated all the way through
134
  // your tree parsers etc as the token does not store the text it just refers
135
  // back to the input stream and trying to get the text for it will abort if you
136
  // close the input stream too early.
137
  //
138
7
  inc.push_back( in );
139
140
  //TODO what said before
141
7
  return true;
142
}
143
144
7
void Tptp::includeFile(std::string fileName) {
145
  // security for online version
146
7
  if(!canIncludeFile()) {
147
    parseError("include-file feature was disabled for this run.");
148
  }
149
150
  // Get the lexer
151
7
  AntlrInput * ai = static_cast<AntlrInput *>(getInput());
152
7
  pANTLR3_LEXER lexer = ai->getAntlr3Lexer();
153
154
  // push the inclusion scope; will be popped by our special popCharStream
155
  // would be necessary for handling symbol filtering in inclusions
156
  //pushScope();
157
158
  // get the name of the current stream "Does it work inside an include?"
159
7
  const std::string inputName = ai->getInputStreamName();
160
161
  // Test in the directory of the actual parsed file
162
7
  std::string currentDirFileName;
163
7
  if(inputName != "<stdin>") {
164
    // TODO: Use dirname or Boost::filesystem?
165
7
    size_t pos = inputName.rfind('/');
166
7
    if(pos != std::string::npos) {
167
      currentDirFileName = std::string(inputName, 0, pos + 1);
168
    }
169
7
    currentDirFileName.append(fileName);
170
7
    if(newInputStream(currentDirFileName,lexer, d_in_created)) {
171
7
      return;
172
    }
173
  } else {
174
    currentDirFileName = "<unknown current directory for stdin>";
175
  }
176
177
  if(d_tptpDir.empty()) {
178
    parseError("Couldn't open included file: " + fileName
179
               + " at " + currentDirFileName + " and the TPTP directory is not specified (environment variable TPTP)");
180
  };
181
182
  std::string tptpDirFileName = d_tptpDir + fileName;
183
  if(! newInputStream(tptpDirFileName,lexer, d_in_created)) {
184
    parseError("Couldn't open included file: " + fileName
185
               + " at " + currentDirFileName + " or " + tptpDirFileName);
186
  }
187
}
188
189
3
void Tptp::checkLetBinding(const std::vector<api::Term>& bvlist,
190
                           api::Term lhs,
191
                           api::Term rhs,
192
                           bool formula)
193
{
194
3
  if (lhs.getKind() != api::APPLY_UF)
195
  {
196
    parseError("malformed let: LHS must be a flat function application");
197
  }
198
6
  const std::multiset<api::Term> vars{lhs.begin(), lhs.end()};
199
3
  if (formula && !lhs.getSort().isBoolean())
200
  {
201
    parseError("malformed let: LHS must be formula");
202
  }
203
11
  for (const cvc5::api::Term& var : vars)
204
  {
205
8
    if (var.hasOp())
206
    {
207
      parseError("malformed let: LHS must be flat, illegal child: " +
208
                 var.toString());
209
    }
210
  }
211
212
  // ensure all let-bound variables appear on the LHS, and appear only once
213
8
  for (const api::Term& bound_var : bvlist)
214
  {
215
5
    const size_t count = vars.count(bound_var);
216
5
    if (count == 0) {
217
      parseError(
218
          "malformed let: LHS must make use of all quantified variables, "
219
          "missing `" +
220
          bound_var.toString() + "'");
221
5
    } else if (count >= 2) {
222
      parseError("malformed let: LHS cannot use same bound variable twice: " +
223
                 bound_var.toString());
224
    }
225
  }
226
3
}
227
228
5258
api::Term Tptp::parseOpToExpr(ParseOp& p)
229
{
230
10516
  api::Term expr;
231
5258
  if (!p.d_expr.isNull())
232
  {
233
    return p.d_expr;
234
  }
235
  // if it has a kind, it's a builtin one and this function should not have been
236
  // called
237
5258
  Assert(p.d_kind == api::NULL_EXPR);
238
5258
  if (isDeclared(p.d_name))
239
  {  // already appeared
240
5174
    expr = getVariable(p.d_name);
241
  }
242
  else
243
  {
244
    api::Sort t =
245
168
        p.d_type == d_solver->getBooleanSort() ? p.d_type : d_unsorted;
246
84
    expr = bindVar(p.d_name, t, true);  // must define at level zero
247
84
    preemptCommand(new DeclareFunctionCommand(p.d_name, expr, t));
248
  }
249
5258
  return expr;
250
}
251
252
896
api::Term Tptp::applyParseOp(ParseOp& p, std::vector<api::Term>& args)
253
{
254
896
  if (Debug.isOn("parser"))
255
  {
256
    Debug("parser") << "applyParseOp: " << p << " to:" << std::endl;
257
    for (std::vector<api::Term>::iterator i = args.begin(); i != args.end();
258
         ++i)
259
    {
260
      Debug("parser") << "++ " << *i << std::endl;
261
    }
262
  }
263
896
  Assert(!args.empty());
264
  // If operator already defined, just build application
265
896
  if (!p.d_expr.isNull())
266
  {
267
    // this happens with some arithmetic kinds, which are wrapped around
268
    // lambdas.
269
27
    args.insert(args.begin(), p.d_expr);
270
27
    return d_solver->mkTerm(api::APPLY_UF, args);
271
  }
272
869
  bool isBuiltinKind = false;
273
  // the builtin kind of the overall return expression
274
869
  api::Kind kind = api::NULL_EXPR;
275
  // First phase: piece operator together
276
869
  if (p.d_kind == api::NULL_EXPR)
277
  {
278
    // A non-built-in function application, get the expression
279
1010
    api::Term v;
280
505
    if (isDeclared(p.d_name))
281
    {  // already appeared
282
439
      v = getVariable(p.d_name);
283
    }
284
    else
285
    {
286
132
      std::vector<api::Sort> sorts(args.size(), d_unsorted);
287
      api::Sort t =
288
132
          p.d_type == d_solver->getBooleanSort() ? p.d_type : d_unsorted;
289
66
      t = d_solver->mkFunctionSort(sorts, t);
290
66
      v = bindVar(p.d_name, t, true);  // must define at level zero
291
66
      preemptCommand(new DeclareFunctionCommand(p.d_name, v, t));
292
    }
293
    // args might be rationals, in which case we need to create
294
    // distinct constants of the "unsorted" sort to represent them
295
1213
    for (size_t i = 0; i < args.size(); ++i)
296
    {
297
2124
      if (args[i].getSort().isReal()
298
2124
          && v.getSort().getFunctionDomainSorts()[i] == d_unsorted)
299
      {
300
        args[i] = convertRatToUnsorted(args[i]);
301
      }
302
    }
303
505
    Assert(!v.isNull());
304
505
    checkFunctionLike(v);
305
505
    kind = getKindForFunction(v);
306
505
    args.insert(args.begin(), v);
307
  }
308
  else
309
  {
310
364
    kind = p.d_kind;
311
364
    isBuiltinKind = true;
312
  }
313
869
  Assert(kind != api::NULL_EXPR);
314
869
  const Options& opts = d_solver->getOptions();
315
  // Second phase: apply parse op to the arguments
316
869
  if (isBuiltinKind)
317
  {
318
364
    if (!opts.getUfHo() && (kind == api::EQUAL || kind == api::DISTINCT))
319
    {
320
      // need --uf-ho if these operators are applied over function args
321
201
      for (std::vector<api::Term>::iterator i = args.begin(); i != args.end();
322
           ++i)
323
      {
324
134
        if ((*i).getSort().isFunction())
325
        {
326
          parseError(
327
              "Cannot apply equalty to functions unless --uf-ho is set.");
328
        }
329
      }
330
    }
331
1092
    if (!strictModeEnabled() && (kind == api::AND || kind == api::OR)
332
426
        && args.size() == 1)
333
    {
334
      // Unary AND/OR can be replaced with the argument.
335
      return args[0];
336
    }
337
364
    if (kind == api::MINUS && args.size() == 1)
338
    {
339
      return d_solver->mkTerm(api::UMINUS, args[0]);
340
    }
341
364
    if (kind == api::TO_REAL)
342
    {
343
      // If the type is real, this is a no-op. We require this special
344
      // case in the TPTP parser since TO_REAL is designed to match the
345
      // SMT-LIB operator, meaning it can only be applied to integers, whereas
346
      // the TPTP to_real / to_rat do not have the same semantics.
347
11
      api::Sort s = args[0].getSort();
348
8
      if (s.isReal())
349
      {
350
5
        return args[0];
351
      }
352
    }
353
359
    return d_solver->mkTerm(kind, args);
354
  }
355
356
  // check if partially applied function, in this case we use HO_APPLY
357
505
  if (args.size() >= 2)
358
  {
359
1010
    api::Sort argt = args[0].getSort();
360
505
    if (argt.isFunction())
361
    {
362
505
      unsigned arity = argt.getFunctionArity();
363
505
      if (args.size() - 1 < arity)
364
      {
365
        if (!opts.getUfHo())
366
        {
367
          parseError("Cannot partially apply functions unless --uf-ho is set.");
368
        }
369
        Debug("parser") << "Partial application of " << args[0];
370
        Debug("parser") << " : #argTypes = " << arity;
371
        Debug("parser") << ", #args = " << args.size() - 1 << std::endl;
372
        // must curry the partial application
373
        return d_solver->mkTerm(api::HO_APPLY, args);
374
      }
375
    }
376
  }
377
505
  return d_solver->mkTerm(kind, args);
378
}
379
380
70
api::Term Tptp::mkDecimal(
381
    std::string& snum, std::string& sden, bool pos, size_t exp, bool posE)
382
{
383
  // the numerator and the denominator
384
140
  std::stringstream ssn;
385
140
  std::stringstream ssd;
386
70
  if (exp != 0)
387
  {
388
16
    if (posE)
389
    {
390
      // see if we need to pad zeros on the end, e.g. 1.2E5 ---> 120000
391
10
      if (exp >= sden.size())
392
      {
393
10
        ssn << snum << sden;
394
4953
        for (size_t i = 0, nzero = (exp - sden.size()); i < nzero; i++)
395
        {
396
4943
          ssn << "0";
397
        }
398
10
        ssd << "0";
399
      }
400
      else
401
      {
402
        ssn << snum << sden.substr(0, exp);
403
        ssd << sden.substr(exp);
404
      }
405
    }
406
    else
407
    {
408
      // see if we need to pad zeros on the beginning, e.g. 1.2E-5 ---> 0.000012
409
6
      if (exp >= snum.size())
410
      {
411
6
        ssn << "0";
412
3300
        for (size_t i = 0, nzero = (exp - snum.size()); i < nzero; i++)
413
        {
414
3294
          ssd << "0";
415
        }
416
6
        ssd << snum << sden;
417
      }
418
      else
419
      {
420
        ssn << snum.substr(0, exp);
421
        ssd << snum.substr(exp) << sden;
422
      }
423
    }
424
  }
425
  else
426
  {
427
54
    ssn << snum;
428
54
    ssd << sden;
429
  }
430
140
  std::stringstream ss;
431
70
  if (!pos)
432
  {
433
11
    ss << "-";
434
  }
435
70
  ss << ssn.str() << "." << ssd.str();
436
140
  return d_solver->mkReal(ss.str());
437
}
438
439
1
void Tptp::forceLogic(const std::string& logic)
440
{
441
1
  Parser::forceLogic(logic);
442
1
  preemptCommand(new SetBenchmarkLogicCommand(logic));
443
1
}
444
445
59
void Tptp::addFreeVar(api::Term var)
446
{
447
59
  Assert(cnf());
448
59
  d_freeVar.push_back(var);
449
59
}
450
451
85
std::vector<api::Term> Tptp::getFreeVar()
452
{
453
85
  Assert(cnf());
454
85
  std::vector<api::Term> r;
455
85
  r.swap(d_freeVar);
456
85
  return r;
457
}
458
459
42
api::Term Tptp::convertRatToUnsorted(api::Term expr)
460
{
461
  // Create the conversion function If they doesn't exists
462
42
  if (d_rtu_op.isNull()) {
463
12
    api::Sort t;
464
    // Conversion from rational to unsorted
465
6
    t = d_solver->mkFunctionSort(d_solver->getRealSort(), d_unsorted);
466
6
    d_rtu_op = d_solver->mkConst(t, "$$rtu");
467
6
    preemptCommand(new DeclareFunctionCommand("$$rtu", d_rtu_op, t));
468
    // Conversion from unsorted to rational
469
6
    t = d_solver->mkFunctionSort(d_unsorted, d_solver->getRealSort());
470
6
    d_utr_op = d_solver->mkConst(t, "$$utr");
471
6
    preemptCommand(new DeclareFunctionCommand("$$utr", d_utr_op, t));
472
  }
473
  // Add the inverse in order to show that over the elements that
474
  // appear in the problem there is a bijection between unsorted and
475
  // rational
476
84
  api::Term ret = d_solver->mkTerm(api::APPLY_UF, d_rtu_op, expr);
477
42
  if (d_r_converted.find(expr) == d_r_converted.end()) {
478
32
    d_r_converted.insert(expr);
479
32
    api::Term eq = d_solver->mkTerm(
480
64
        api::EQUAL, expr, d_solver->mkTerm(api::APPLY_UF, d_utr_op, ret));
481
32
    preemptCommand(new AssertCommand(eq));
482
  }
483
84
  return api::Term(ret);
484
}
485
486
6
api::Term Tptp::convertStrToUnsorted(std::string str)
487
{
488
6
  api::Term& e = d_distinct_objects[str];
489
6
  if (e.isNull())
490
  {
491
6
    e = d_solver->mkConst(d_unsorted, str);
492
  }
493
6
  return e;
494
}
495
496
1
api::Term Tptp::mkLambdaWrapper(api::Kind k, api::Sort argType)
497
{
498
2
  Debug("parser") << "mkLambdaWrapper: kind " << k << " and type " << argType
499
1
                  << "\n";
500
2
  std::vector<api::Term> lvars;
501
2
  std::vector<api::Sort> domainTypes = argType.getFunctionDomainSorts();
502
2
  for (unsigned i = 0, size = domainTypes.size(); i < size; ++i)
503
  {
504
    // the introduced variable is internal (not parsable)
505
2
    std::stringstream ss;
506
1
    ss << "_lvar_" << i;
507
2
    api::Term v = d_solver->mkVar(domainTypes[i], ss.str());
508
1
    lvars.push_back(v);
509
  }
510
  // apply body of lambda to variables
511
  api::Term wrapper =
512
1
      d_solver->mkTerm(api::LAMBDA,
513
2
                       d_solver->mkTerm(api::BOUND_VAR_LIST, lvars),
514
3
                       d_solver->mkTerm(k, lvars));
515
516
2
  return wrapper;
517
}
518
519
1319
api::Term Tptp::getAssertionExpr(FormulaRole fr, api::Term expr)
520
{
521
1319
  switch (fr) {
522
1292
    case FR_AXIOM:
523
    case FR_HYPOTHESIS:
524
    case FR_DEFINITION:
525
    case FR_ASSUMPTION:
526
    case FR_LEMMA:
527
    case FR_THEOREM:
528
    case FR_NEGATED_CONJECTURE:
529
    case FR_PLAIN:
530
      // it's a usual assert
531
1292
      return expr;
532
24
    case FR_CONJECTURE:
533
      // it should be negated when asserted
534
24
      return d_solver->mkTerm(api::NOT, expr);
535
3
    case FR_UNKNOWN:
536
    case FR_FI_DOMAIN:
537
    case FR_FI_FUNCTORS:
538
    case FR_FI_PREDICATES:
539
    case FR_TYPE:
540
      // it does not correspond to an assertion
541
3
      return d_nullExpr;
542
      break;
543
  }
544
  Assert(false);  // unreachable
545
  return d_nullExpr;
546
}
547
548
43
api::Term Tptp::getAssertionDistinctConstants()
549
{
550
86
  std::vector<api::Term> constants;
551
49
  for (std::pair<const std::string, api::Term>& cs : d_distinct_objects)
552
  {
553
6
    constants.push_back(cs.second);
554
  }
555
43
  if (constants.size() > 1)
556
  {
557
3
    return d_solver->mkTerm(api::DISTINCT, constants);
558
  }
559
40
  return d_nullExpr;
560
}
561
562
1319
Command* Tptp::makeAssertCommand(FormulaRole fr,
563
                                 api::Term expr,
564
                                 bool cnf,
565
                                 bool inUnsatCore)
566
{
567
  // For SZS ontology compliance.
568
  // if we're in cnf() though, conjectures don't result in "Theorem" or
569
  // "CounterSatisfiable".
570
1319
  if (!cnf && (fr == FR_NEGATED_CONJECTURE || fr == FR_CONJECTURE)) {
571
24
    d_hasConjecture = true;
572
24
    Assert(!expr.isNull());
573
  }
574
1319
  if( expr.isNull() ){
575
3
    return new EmptyCommand("Untreated role for expression");
576
  }else{
577
1316
    return new AssertCommand(expr, inUnsatCore);
578
  }
579
}
580
581
}  // namespace parser
582
28182
}  // namespace cvc5