Repository: HarvardPL/formulog Branch: master Commit: e1e7f6ffe3b7 Files: 664 Total size: 1.4 MB Directory structure: gitextract_eqja_ilo/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── maven.yml │ ├── pages.yml │ └── submit-deps.yml ├── .gitignore ├── Dockerfile ├── LICENSE.txt ├── README.md ├── changelog.md ├── docs/ │ ├── Gemfile │ ├── _config.yml │ ├── eval_modes/ │ │ ├── compile.md │ │ ├── eager.md │ │ ├── index.md │ │ ├── options.md │ │ └── smt.md │ ├── index.md │ ├── lang_ref/ │ │ ├── goal_directed_eval.md │ │ ├── index.md │ │ ├── lang_basics.md │ │ ├── logical_formulas.md │ │ └── program_safety.md │ ├── pubs.md │ ├── starting.md │ └── tutorial/ │ └── index.md ├── examples/ │ ├── greeting.flg │ ├── liquid_types.flg │ ├── symeval.flg │ └── tutorial.flg ├── license-header ├── misc/ │ └── flg.vim ├── pom.xml └── src/ ├── main/ │ ├── antlr4/ │ │ └── edu/ │ │ └── harvard/ │ │ └── seas/ │ │ └── pl/ │ │ └── formulog/ │ │ └── parsing/ │ │ └── generated/ │ │ └── Formulog.g4 │ ├── java/ │ │ └── edu/ │ │ └── harvard/ │ │ └── seas/ │ │ └── pl/ │ │ └── formulog/ │ │ ├── Configuration.java │ │ ├── FormulogTester.java │ │ ├── Main.java │ │ ├── PrintPreference.java │ │ ├── ast/ │ │ │ ├── AbstractRule.java │ │ │ ├── AbstractTerm.java │ │ │ ├── BasicProgram.java │ │ │ ├── BasicRule.java │ │ │ ├── BindingType.java │ │ │ ├── BoolTerm.java │ │ │ ├── ComplexLiteral.java │ │ │ ├── ComplexLiterals.java │ │ │ ├── Constructor.java │ │ │ ├── Constructors.java │ │ │ ├── Expr.java │ │ │ ├── Exprs.java │ │ │ ├── FP32.java │ │ │ ├── FP64.java │ │ │ ├── Fold.java │ │ │ ├── FunctionCallFactory.java │ │ │ ├── Functor.java │ │ │ ├── I32.java │ │ │ ├── I64.java │ │ │ ├── LetFunExpr.java │ │ │ ├── Literal.java │ │ │ ├── MatchClause.java │ │ │ ├── MatchExpr.java │ │ │ ├── Model.java │ │ │ ├── NestedFunctionDef.java │ │ │ ├── OpaqueSet.java │ │ │ ├── Primitive.java │ │ │ ├── Program.java │ │ │ ├── Rule.java │ │ │ ├── SmtLibTerm.java │ │ │ ├── StringTerm.java │ │ │ ├── Term.java │ │ │ ├── Terms.java │ │ │ ├── UnificationPredicate.java │ │ │ ├── UserPredicate.java │ │ │ └── Var.java │ │ ├── codegen/ │ │ │ ├── CodeGen.java │ │ │ ├── CodeGenContext.java │ │ │ ├── CodeGenException.java │ │ │ ├── CodeGenUtil.java │ │ │ ├── DeltaFirstQueryPlanner.java │ │ │ ├── FuncsHpp.java │ │ │ ├── FunctorCodeGen.java │ │ │ ├── MainCpp.java │ │ │ ├── MatchCodeGen.java │ │ │ ├── NopQueryPlanner.java │ │ │ ├── PatternMatchTree.java │ │ │ ├── QueryPlanner.java │ │ │ ├── RuleTranslator.java │ │ │ ├── SmtParserCpp.java │ │ │ ├── SmtShimCpp.java │ │ │ ├── SouffleCodeGen.java │ │ │ ├── SymbolCpp.java │ │ │ ├── SymbolHpp.java │ │ │ ├── TemplateSrcFile.java │ │ │ ├── TermCodeGen.java │ │ │ ├── TermCpp.java │ │ │ ├── TypeCodeGen.java │ │ │ ├── TypeCpp.java │ │ │ └── ast/ │ │ │ ├── cpp/ │ │ │ │ ├── CppAccess.java │ │ │ │ ├── CppBaseTerm.java │ │ │ │ ├── CppBinop.java │ │ │ │ ├── CppBlock.java │ │ │ │ ├── CppCast.java │ │ │ │ ├── CppConst.java │ │ │ │ ├── CppCtor.java │ │ │ │ ├── CppDecl.java │ │ │ │ ├── CppExpr.java │ │ │ │ ├── CppExprFromString.java │ │ │ │ ├── CppFor.java │ │ │ │ ├── CppForEach.java │ │ │ │ ├── CppFuncCall.java │ │ │ │ ├── CppGoto.java │ │ │ │ ├── CppIf.java │ │ │ │ ├── CppLabel.java │ │ │ │ ├── CppMethodCall.java │ │ │ │ ├── CppNewArray.java │ │ │ │ ├── CppNullptr.java │ │ │ │ ├── CppReturn.java │ │ │ │ ├── CppSeq.java │ │ │ │ ├── CppStmt.java │ │ │ │ ├── CppSubscript.java │ │ │ │ ├── CppUnop.java │ │ │ │ ├── CppVar.java │ │ │ │ ├── CppVectorLiteral.java │ │ │ │ └── CppWhile.java │ │ │ └── souffle/ │ │ │ ├── SAtom.java │ │ │ ├── SDestructorBody.java │ │ │ ├── SExprBody.java │ │ │ ├── SFunctorBody.java │ │ │ ├── SFunctorCall.java │ │ │ ├── SInfixBinaryOpAtom.java │ │ │ ├── SInt.java │ │ │ ├── SIntList.java │ │ │ ├── SIntListType.java │ │ │ ├── SIntType.java │ │ │ ├── SLit.java │ │ │ ├── SRule.java │ │ │ ├── SRuleMode.java │ │ │ ├── STerm.java │ │ │ ├── SType.java │ │ │ └── SVar.java │ │ ├── db/ │ │ │ ├── BindingTypeArrayWrapper.java │ │ │ ├── ExampleComparator.java │ │ │ ├── IndexedFactDb.java │ │ │ ├── IndexedFactDbBuilder.java │ │ │ ├── MinChainCover.java │ │ │ ├── MinIndex.java │ │ │ ├── SortedIndexedFactDb.java │ │ │ └── TupleComparatorGenerator.java │ │ ├── eval/ │ │ │ ├── AbstractStratumEvaluator.java │ │ │ ├── EagerStratumEvaluator.java │ │ │ ├── EvalUtil.java │ │ │ ├── Evaluation.java │ │ │ ├── EvaluationException.java │ │ │ ├── EvaluationResult.java │ │ │ ├── IndexedRule.java │ │ │ ├── PredicateFunctionSetter.java │ │ │ ├── RoundBasedStratumEvaluator.java │ │ │ ├── SemiNaiveEvaluation.java │ │ │ ├── SemiNaiveRule.java │ │ │ ├── SmtCallFinder.java │ │ │ ├── StratumEvaluator.java │ │ │ └── UncheckedEvaluationException.java │ │ ├── functions/ │ │ │ ├── BuiltInFunctionDefFactory.java │ │ │ ├── DummyFunctionDef.java │ │ │ ├── FunctionDef.java │ │ │ ├── FunctionDefManager.java │ │ │ ├── OpaqueSetOps.java │ │ │ ├── PredicateFunctionDef.java │ │ │ ├── PrimitiveConversions.java │ │ │ ├── RecordAccessor.java │ │ │ └── UserFunctionDef.java │ │ ├── magic/ │ │ │ ├── AdornedSymbol.java │ │ │ ├── Adornments.java │ │ │ └── MagicSetTransformer.java │ │ ├── parsing/ │ │ │ ├── FactFileParser.java │ │ │ ├── Identifier.java │ │ │ ├── ParseException.java │ │ │ ├── Parser.java │ │ │ ├── ParsingContext.java │ │ │ ├── ParsingUtil.java │ │ │ ├── TermExtractor.java │ │ │ ├── TopLevelParser.java │ │ │ ├── TypeExtractor.java │ │ │ ├── UncheckedParseException.java │ │ │ ├── VariableCheckPass.java │ │ │ └── VariableCheckPassException.java │ │ ├── smt/ │ │ │ ├── AbstractSmtLibSolver.java │ │ │ ├── BestMatchSmtManager.java │ │ │ ├── BoolectorProcessFactory.java │ │ │ ├── CallAndResetSolver.java │ │ │ ├── CheckSatAssumingSolver.java │ │ │ ├── Cvc4ProcessFactory.java │ │ │ ├── DoubleCheckingSolver.java │ │ │ ├── ExternalSolverProcessFactory.java │ │ │ ├── NotThreadSafeQueueSmtManager.java │ │ │ ├── PerThreadSmtManager.java │ │ │ ├── PushPopNaiveSolver.java │ │ │ ├── PushPopSolver.java │ │ │ ├── QueueSmtManager.java │ │ │ ├── SingleShotSolver.java │ │ │ ├── SmtLibParser.java │ │ │ ├── SmtLibShim.java │ │ │ ├── SmtLibSolver.java │ │ │ ├── SmtResult.java │ │ │ ├── SmtStatus.java │ │ │ ├── SmtStrategy.java │ │ │ ├── YicesProcessFactory.java │ │ │ └── Z3ProcessFactory.java │ │ ├── symbols/ │ │ │ ├── AbstractSymbol.java │ │ │ ├── AbstractTypedSymbol.java │ │ │ ├── AbstractWrappedRelationSymbol.java │ │ │ ├── BuiltInConstructorGetterSymbol.java │ │ │ ├── BuiltInConstructorSymbol.java │ │ │ ├── BuiltInConstructorTesterSymbol.java │ │ │ ├── BuiltInFunctionSymbol.java │ │ │ ├── BuiltInTypeSymbol.java │ │ │ ├── ConstructorSymbol.java │ │ │ ├── ConstructorSymbolImpl.java │ │ │ ├── ConstructorSymbolType.java │ │ │ ├── FunctionSymbol.java │ │ │ ├── GlobalSymbolManager.java │ │ │ ├── MutableRelationSymbol.java │ │ │ ├── PredicateFunctionSymbol.java │ │ │ ├── RecordSymbol.java │ │ │ ├── RelationSymbol.java │ │ │ ├── Symbol.java │ │ │ ├── SymbolComparator.java │ │ │ ├── SymbolManager.java │ │ │ ├── TypeSymbol.java │ │ │ ├── TypeSymbolImpl.java │ │ │ ├── TypeSymbolType.java │ │ │ ├── TypedSymbol.java │ │ │ ├── WrappedRelationSymbol.java │ │ │ └── parameterized/ │ │ │ ├── AbstractParameterizedSymbol.java │ │ │ ├── BuiltInConstructorSymbolBase.java │ │ │ ├── FunctorBase.java │ │ │ ├── Param.java │ │ │ ├── ParamKind.java │ │ │ ├── ParameterizedConstructorSymbol.java │ │ │ ├── ParameterizedSymbol.java │ │ │ └── SymbolBase.java │ │ ├── types/ │ │ │ ├── BuiltInTypes.java │ │ │ ├── FunctorType.java │ │ │ ├── IndexedType.java │ │ │ ├── TypeAlias.java │ │ │ ├── TypeChecker.java │ │ │ ├── TypeException.java │ │ │ ├── TypeManager.java │ │ │ ├── Types.java │ │ │ └── WellTypedProgram.java │ │ ├── unification/ │ │ │ ├── EmptySubstitution.java │ │ │ ├── OverwriteSubstitution.java │ │ │ ├── SimpleSubstitution.java │ │ │ ├── Substitution.java │ │ │ └── Unification.java │ │ ├── util/ │ │ │ ├── AbstractFJPTask.java │ │ │ ├── CompositeIterable.java │ │ │ ├── CountingFJP.java │ │ │ ├── CountingFJPImpl.java │ │ │ ├── Dataset.java │ │ │ ├── DedupWorkList.java │ │ │ ├── EnumerableThreadLocal.java │ │ │ ├── ExceptionalFunction.java │ │ │ ├── FunctorUtil.java │ │ │ ├── IntArrayWrapper.java │ │ │ ├── MockCountingFJP.java │ │ │ ├── Pair.java │ │ │ ├── SharedLong.java │ │ │ ├── StackMap.java │ │ │ ├── TodoException.java │ │ │ ├── Triple.java │ │ │ ├── UnionFind.java │ │ │ ├── Util.java │ │ │ └── sexp/ │ │ │ ├── SExp.java │ │ │ ├── SExpAtom.java │ │ │ ├── SExpException.java │ │ │ ├── SExpLexer.java │ │ │ ├── SExpList.java │ │ │ ├── SExpParser.java │ │ │ └── SExpToken.java │ │ └── validating/ │ │ ├── FunctionDefValidation.java │ │ ├── InvalidProgramException.java │ │ ├── Stratifier.java │ │ ├── Stratum.java │ │ ├── ValidRule.java │ │ └── ast/ │ │ ├── Assignment.java │ │ ├── Check.java │ │ ├── Destructor.java │ │ ├── SimpleLiteral.java │ │ ├── SimpleLiteralExnVisitor.java │ │ ├── SimpleLiteralTag.java │ │ ├── SimpleLiteralVisitor.java │ │ ├── SimplePredicate.java │ │ └── SimpleRule.java │ └── resources/ │ └── codegen/ │ ├── .gitignore │ ├── CMakeLists.txt │ └── src/ │ ├── ConcurrentHashMap.hpp │ ├── Symbol.cpp │ ├── Symbol.hpp │ ├── Term.cpp │ ├── Term.hpp │ ├── Tuple.hpp │ ├── Type.cpp │ ├── Type.hpp │ ├── formulog.dl │ ├── funcs.hpp │ ├── functors.cpp │ ├── functors.h │ ├── globals.h │ ├── main.cpp │ ├── parser.cpp │ ├── parser.hpp │ ├── set.cpp │ ├── set.hpp │ ├── smt_parser.cpp │ ├── smt_parser.hpp │ ├── smt_shim.cpp │ ├── smt_shim.h │ ├── smt_solver.cpp │ ├── smt_solver.h │ └── time.hpp └── test/ ├── java/ │ └── edu/ │ └── harvard/ │ └── seas/ │ └── pl/ │ └── formulog/ │ ├── codegen/ │ │ ├── CodeGenTester.java │ │ ├── CompiledEagerEvaluationTest.java │ │ ├── CompiledEagerMagicSetTest.java │ │ ├── CompiledSemiNaiveEvaluationTest.java │ │ ├── CompiledSemiNaiveMagicSetTest.java │ │ └── NopTester.java │ ├── eval/ │ │ ├── AbstractEvaluationTest.java │ │ ├── AbstractTester.java │ │ ├── CommonEvaluationTest.java │ │ ├── EagerSemiNaiveEvaluationTest.java │ │ ├── InterpretedSemiNaiveTester.java │ │ ├── SemiNaiveEvaluationTest.java │ │ └── Tester.java │ ├── magic/ │ │ ├── CommonMagicSetTest.java │ │ ├── EagerSemiNaiveMagicSetTest.java │ │ └── SemiNaiveMagicSetTest.java │ ├── parsing/ │ │ └── ParsingTest.java │ ├── types/ │ │ └── TypeCheckingTest.java │ └── validating/ │ ├── SemiNaiveValidatingTest.java │ └── ValidatingTest.java └── resources/ ├── test001_ok.flg ├── test002_ok.flg ├── test003_ok.flg ├── test004_ok.flg ├── test005_ok.flg ├── test006_ok.flg ├── test007_ok.flg ├── test008_ok.flg ├── test009_ok.flg ├── test010_ok.flg ├── test011_ok.flg ├── test012_bd.flg ├── test013_ok.flg ├── test014_ok.flg ├── test015_ok.flg ├── test016_ok.flg ├── test017_ok.flg ├── test018_ok.flg ├── test019_ok.flg ├── test020_ok.flg ├── test021_ok.flg ├── test022_ok.flg ├── test023_ok.flg ├── test024_ok.flg ├── test025_bd.flg ├── test026_bd.flg ├── test027_ok.flg ├── test028_ok.flg ├── test029_ok.flg ├── test030_ok.flg ├── test031_ok.flg ├── test032_ok.flg ├── test033_ok.flg ├── test034_ok.flg ├── test035_ok.flg ├── test036_ok.flg ├── test037_ok.flg ├── test038_ok.flg ├── test039_ok.flg ├── test040_ok.flg ├── test041_ok.flg ├── test042_ok.flg ├── test043_ok.flg ├── test044_ok.flg ├── test045_ok.flg ├── test046_ok.flg ├── test047_ok.flg ├── test048_ok.flg ├── test049_ok.flg ├── test050_bd.flg ├── test051_bd.flg ├── test052_bd.flg ├── test053_ok.flg ├── test054_ok.flg ├── test055_bd.flg ├── test056_ok.flg ├── test057_ok.flg ├── test058_ok.flg ├── test059_ok.flg ├── test060_ok.flg ├── test061_ok.flg ├── test062_ok.flg ├── test063_ok.flg ├── test064_ok.flg ├── test065_ok.flg ├── test066_ok.flg ├── test067_ok.flg ├── test068_ok.flg ├── test069_ok.flg ├── test070_ok.flg ├── test071_ok.flg ├── test072_ok.flg ├── test073_ok.flg ├── test074_ok.flg ├── test075_ok.flg ├── test076_ok.flg ├── test077_ok.flg ├── test078_ok.flg ├── test079_ok.flg ├── test080_bd.flg ├── test081_ok.flg ├── test082_ok.flg ├── test083_ok.flg ├── test084_ok.flg ├── test085_ok.flg ├── test086_ok.flg ├── test087_ok.flg ├── test088_ok.flg ├── test089_ok.flg ├── test090_ok.flg ├── test091_bd.flg ├── test092_ok.flg ├── test093_ok.flg ├── test094_ok.flg ├── test095_ok.flg ├── test096_ok.flg ├── test097_ok.flg ├── test098_bd.flg ├── test099_ok.flg ├── test100_ok.flg ├── test101_ok.flg ├── test102_ok.flg ├── test103_ok.flg ├── test104_ok.flg ├── test105_ok.flg ├── test106_ok.flg ├── test107_ok.flg ├── test108_ok.flg ├── test109_ok.flg ├── test110_ok.flg ├── test111_ok.flg ├── test112_ok.flg ├── test113_ok.flg ├── test114_ok.flg ├── test115_ok.flg ├── test116_ok.flg ├── test117_ok.flg ├── test118_bd.flg ├── test119_ok.flg ├── test120_ok.flg ├── test121_ok.flg ├── test122_ok.flg ├── test123_ok.flg ├── test124_ok.flg ├── test125_ok.flg ├── test126_ok.flg ├── test127_ok.flg ├── test128_ok.flg ├── test129_ok.flg ├── test130_bd.flg ├── test131_bd.flg ├── test132_bd.flg ├── test133_ok.flg ├── test134_ok.flg ├── test135_ok.flg ├── test136_ok.flg ├── test137_ok.flg ├── test138_ok.flg ├── test139_ok.flg ├── test140_ok.flg ├── test141_ok.flg ├── test142_ok.flg ├── test143_ok.flg ├── test144_ok.flg ├── test145_ok.flg ├── test146_ok.flg ├── test147_ok.flg ├── test148_ok.flg ├── test149_ok.flg ├── test150_ok.flg ├── test151_ok.flg ├── test152_ok.flg ├── test153_ok.flg ├── test154_ok.flg ├── test155_ok.flg ├── test156_ok.flg ├── test157_ok.flg ├── test158_ok.flg ├── test159_ok.flg ├── test160_ok.flg ├── test161_ok.flg ├── test162_ok.flg ├── test163_ok.flg ├── test164_ok.flg ├── test165_ok.flg ├── test166_ok.flg ├── test167_ok.flg ├── test168_ok.flg ├── test169_ok.flg ├── test170_ok.flg ├── test171_ok.flg ├── test172_ok.flg ├── test173_ok.flg ├── test174_ok.flg ├── test175_ok.flg ├── test176_ok.flg ├── test177_ok.flg ├── test178_ok.flg ├── test179_ok.flg ├── test180_ok.flg ├── test181_ok.flg ├── test182_ok.flg ├── test183_ok.flg ├── test184_ok.flg ├── test185_bd.flg ├── test186_ok.flg ├── test187_ok.flg ├── test188_bd.flg ├── test189_ok.flg ├── test190_ok.flg ├── test191_input/ │ ├── complex_terms.tsv │ ├── names.tsv │ └── numbers.tsv ├── test191_ok.flg ├── test192_ok.flg ├── test193_ok.flg ├── test217_bd.flg ├── test218_bd.flg ├── test219_ok.flg ├── test220_ok.flg ├── test221_ok.flg ├── test222_ok.flg ├── test223_ok.flg ├── test224_ok.flg ├── test225_ok.flg ├── test226_ok.flg ├── test227_ok.flg ├── test228_ok.flg ├── test229_ok.flg ├── test230_ok.flg ├── test231_ok.flg ├── test232_ok.flg ├── test233_ok.flg ├── test234_ok.flg ├── test235_ok.flg ├── test236_ok.flg ├── test237_ok.flg ├── test238_ok.flg ├── test240_ok.flg ├── test241_ok.flg ├── test242_ok.flg ├── test243_ok.flg ├── test244_ok.flg ├── test245_ok.flg ├── test248_bd.flg ├── test249_ok.flg ├── test250_ok.flg ├── test251_bd.flg ├── test252_ok.flg ├── test253_ok.flg ├── test254_ok.flg ├── test255_bd.flg ├── test256_ok.flg ├── test257_ok.flg ├── test258_ok.flg ├── test259_ok.flg ├── test260_ok.flg ├── test261_ok.flg ├── test262_ok.flg ├── test263_ok.flg ├── test264_ok.flg ├── test265_ok.flg ├── test266_bd.flg ├── test267_ok.flg ├── test268_bd.flg ├── test269_bd.flg ├── test270_bd.flg ├── test271_bd.flg ├── test272_bd.flg ├── test273_ok.flg ├── test274_ok.flg ├── test275_ok.flg ├── test276_inputA/ │ └── foo.tsv ├── test276_inputB/ │ └── foo.tsv ├── test276_ok.flg ├── test277_ok.flg ├── test278_ok.flg ├── test279_ok.flg ├── test280_ok.flg ├── test281_ok.flg ├── test282_ok.flg ├── test283_ok.flg ├── test284_ok.flg ├── test285_ok.flg ├── test286_ok.flg ├── test287_ok.flg ├── test288_ok.flg ├── test289_ok.flg ├── test290_ok.flg ├── test291_ok.flg ├── test292_ok.flg ├── test293_ok.flg ├── test294_ok.flg ├── test295_bd.flg ├── test296_bd.flg ├── test297_ok.flg ├── test298_ok.flg ├── test299_ok.flg ├── test300_ok.flg ├── test301_ok.flg ├── test302_bd.flg ├── test303_ok.flg ├── test304_ok.flg ├── test305_ok.flg ├── test306_ok.flg ├── test307_ok.flg ├── test308_ok.flg ├── test309_ok.flg ├── test310_ok.flg ├── test311_ok.flg ├── test312_bd.flg ├── test313_bd.flg ├── test314_bd.flg ├── test315_bd.flg ├── test316_ok.flg ├── test317_ok.flg ├── test318_ok.flg ├── test319_ok.flg ├── test320_ok.flg ├── test321_ok.flg ├── test322_bd.flg ├── test323_ok.flg ├── test324_ok.flg ├── test325_ok.flg ├── test326_ok.flg ├── test327_bd.flg ├── test328_ok.flg ├── test329_ok.flg ├── test330_ok.flg ├── test331_ok.flg ├── test332_ok.flg ├── test333_bd.flg ├── test334_ok.flg ├── test335_bd.flg ├── test336_ok.flg ├── test337_ok.flg ├── test338_ok.flg ├── test339_ok.flg ├── test340_ok.flg ├── test341_bd.flg ├── test342_bd.flg ├── test343_bd.flg ├── test344_bd.flg └── test345_ok.flg ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "maven" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/maven.yml ================================================ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Java CI with Maven on: push: branches: ["master"] paths: - "src/**" - "pom.xml" pull_request: branches: ["master"] paths: - "src/**" - "pom.xml" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Z3 uses: pavpanchekha/setup-z3@v1.3 with: version: '4.12.2' - name: Set up JDK 21 uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' cache: maven - name: Verify with Maven (includes building) run: mvn -B verify --file pom.xml ================================================ FILE: .github/workflows/pages.yml ================================================ # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # Sample workflow for building and deploying a Jekyll site to GitHub Pages name: Deploy Jekyll site to Pages on: push: branches: ["master"] paths: ["docs/**"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true jobs: # Build job build: runs-on: ubuntu-latest defaults: run: working-directory: docs steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.3' # Not needed with a .ruby-version file bundler-cache: true # runs 'bundle install' and caches installed gems automatically cache-version: 0 # Increment this number if you need to re-download cached gems working-directory: '${{ github.workspace }}/docs' - name: Setup Pages id: pages uses: actions/configure-pages@v5 - name: Build with Jekyll # Outputs to the './_site' directory by default run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" env: JEKYLL_ENV: production - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default uses: actions/upload-pages-artifact@v3 with: path: "docs/_site/" # Deployment job deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/submit-deps.yml ================================================ # Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive name: Submit Maven Dependencies on: push: branches: ["master"] permissions: contents: write jobs: submit-maven-deps: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: java-version: '21' distribution: 'temurin' - name: Submit Maven dependency graph uses: advanced-security/maven-dependency-submission-action@v5 ================================================ FILE: .gitignore ================================================ # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* target/ # Eclipse stuff .classpath .project .settings/ .metadata/ # VSCode stuff .vscode/ # Vim swap files *.swp # VS code workspace *.code-workspace # backup files *~ .DS_Store # Formulog stuff /codegen/ /src/main/resources/codegen/build/ .idea/ # Jekyll _site/ ================================================ FILE: Dockerfile ================================================ # To upload the Docker images to Dockerhub, log into the Docker console, and then run # # docker buildx build --push --platform linux/amd64,linux/arm64 -t aaronbembenek/formulog:X.Y.Z . # # (with the appropriate version number substituted for X.Y.Z). FROM maven:3.8.6-openjdk-11 AS build WORKDIR /root/formulog/ COPY src src COPY pom.xml pom.xml RUN mvn package -DskipTests FROM ubuntu:23.04 ARG version=0.8.0-SNAPSHOT WORKDIR /root/ RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive \ apt-get install -y \ openjdk-11-jre \ z3 \ libboost1.81-all-dev \ libtbb-dev \ bison \ build-essential \ clang \ cmake \ doxygen \ flex \ g++ \ git \ libffi-dev \ libncurses5-dev \ libsqlite3-dev \ make \ mcpp \ python3 \ sqlite3 \ zlib1g-dev \ # Install modified Souffle && git clone --branch eager-eval https://github.com/aaronbembenek/souffle.git \ && cd souffle \ && cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DSOUFFLE_ENABLE_TESTING=OFF \ && cmake --build build -j$(nproc) \ && cmake --build build --target install \ && cmake --build build --target clean WORKDIR /root/formulog/ COPY --from=build /root/formulog/target/formulog-${version}-jar-with-dependencies.jar formulog.jar COPY examples examples ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Formulog ![Build Status](https://github.com/HarvardPL/formulog/actions/workflows/maven.yml/badge.svg) **TL;DR: write SMT-based program analyses (symbolic executors, refinement type checkers, etc.) in an optimized Datalog-like language.** Datalog has proven to be a useful language for implementing a range of program analyses, but analyses that use SMT solving cannot be easily written in traditional versions of Datalog. Formulog sets out to fill this gap by augmenting Datalog with ways to construct and reason about SMT formulas, as well as some first-order functional programming to make life easier. **Why write your SMT-based analysis in Formulog?** 1. By combining logic programming, functional programming, and SMT solving, Formulog makes it possible to encode many analyses declaratively at the level of mathematical specification (e.g., inference rules), closing the gap between specification and implementation---and often revealing bugs in the spec! 2. This high-level encoding makes it possible for Formulog to apply high-level optimizations to your analysis, like automatic parallelization and goal-directed evaluation. 3. Thanks to our [Formulog-to-Soufflé compiler](https://harvardpl.github.io/formulog/eval_modes/compile.html), you can automatically generate a C++ version of the analysis that leverages highly optimized Datalog algorithms and data structures. **Interested?** For more information, check out the [Formulog docs](https://harvardpl.github.io/formulog/) (also available in the [docs](./docs/) directory), including [tips on getting started](https://harvardpl.github.io/formulog/starting.html) and the [language reference](https://harvardpl.github.io/formulog/lang_ref/). To get a sense for what's involved in building a nontrivial SMT-based analysis in Formulog, check out our [tutorial](https://harvardpl.github.io/formulog/tutorial/) on implementing a refinement type checker in Formulog. ## Contributing Contributions to this project are most welcome! Please open a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new) and then link a pull request to it. Pull requests must be in the [Google Java format](https://github.com/google/google-java-format) before being merged. To reformat your code, run `mvn spotless:apply`; you can also check if your code is conformant (without reformatting it) by running `mvn spotless:check`. ## Licensing and Third-Party Libraries Formulog is released under an [Apache 2.0 license](./LICENSE.txt). This project uses third-party libraries. You can generate a list of these libraries and download their associated licenses with this command: ``` mvn license:download-licenses ``` The generated content can be found in the `target/generated-resources/` directory. ================================================ FILE: changelog.md ================================================ # Changelog All notable changes to this project will be documented in this file. ## [0.8.0] - 2024-10-18 ### Added - Support for eager evaluation in both interpreter (`--eager-eval` option) and compiler. - Reorganized documentation and added a lot of new material, including a tutorial. - Apply Google Java format with Maven. - Various improvements to the code generator. ### Changed - Better error reporting for type arity mismatch. - Clean up CI. - Better, more consistent CLI options for interpreter and generated code. ### Fixed - Lexing of arithmetic expressions without spaces. - Various (rare) interpreter bugs. - Various bugs in the C++ runtime and generated code. ## [0.7.0] - 2023-02-14 ### Added - Support for compilation to Souffle/C++ (`--codegen` option). ### Changed - Removed built-in functions `substitute` and `is_free`. ## [0.6.0] - 2022-08-31 ### Added - Support for `bv_to_int`, `int_to_bv`, `bv_extract`, and `bv_concat` formula constructors. - More string manipulation and inspection functions (`substring`, `string_length`, `char_at`, `string_to_list`, `list_to_string`, `string_to_i32`, and `string_to_i64`). - Allow argument annotations in relation declarations. - Option to output relations to disk (`@disk` annotation). - Explicit annotation `@edb` for EDB relations. ### Changed - New and improved command line interface. - New syntax for relation declarations (`rel` instead of `input` and `output`). - Removed `@external` annotation (replaced by `@disk`). ### Fixed - Incorrect (non-`smt`) types for formula constructors. - Various lacunae in documentation. - Various bugs (including some type checking bugs). ## [0.5.0] - 2020-11-15 ### Added - Give line numbers with parse exceptions. ### Changed - Signature of `get_model` (takes a list of propositions now) ### Fixed - Bug in the naive (call-reset) SMT solver strategy. - Bug in `let fun` expressions. ## [0.4.0] - 2020-09-22 ### Added - Eager semi-naive evaluation algorithm. - SMT manager that uses push and pop. - Naive SMT manager that does not do any form of caching. - Optimal index selection. - Ability to interface with different solvers and set logic. ### Changed - Accept .tsv fact files instead of .csv files. - Allow ML variables to be lowercase. - The `debugSmt` option logs SMT calls to separate files by solver, instead of printing them to same stream. - Removed `is_valid_opt` and changed signature of `is_sat_opt` to take a list of `bool smt` terms. ### Fixed - Concurrency bug in the memoization of parameterized constructor symbols. - Bug in the recording of rule evaluation diagnostics. - Bug in the "freshening" of `let fun` expressions. ## [0.3.0] - 2020-02-03 ### Added - Support for `fold` terms. - Options to restrict which results are printed after evaluation. - Nested, variable-capturing functions (i.e., `let fun` expressions). - Added generic serialization function `to_string`. - Preliminary (and undocumented) option to compile Formulog program to C++ instead of interpreting it. ### Changed - Do not require parentheses around tuple types. - Do not reorder literals in rules (in order to preserve soundness of flow-sensitive type checking). - Changed the names of some string-related built-in functions (`strcat` is now `string_concat` and `strcmp` is now `string_cmp`). - Removed built-in function `string_of_i32`. ### Fixed - Made Antlr parser faster by simplifying grammar. - Changed precedence of infix cons operator (i.e., `::`). - Added parameterized constructor symbols to fix type soundness issues arising from the non-determinate type signatures of some formula constructors. - Made type checking flow-sensitive to soundly handle destruction of user-defined constructors in formulas. ## [0.2.0] - 2019-11-25 ### Added - Support wild card term `??` when "invoking" predicates as functions. - Constant array constructor `array_const` (from Z3's theory of arrays). - Ability to do partial magic set rewriting with annotations `@bottomup` and `@topdown`. - Demand transformation simplification for magic set rewriting (following Tekle and Liu [2010]). - Support for record types. - Support external input facts via annotation `@external`. - Support sequential runtime (for debugging) via `sequential` system property. - Support existential anonymous variables in negated atoms. ### Changed - Increased the amount of information printed with the `debugMst` option. - Allow ML-style expressions to occur as logic programming terms. - Prefix names of automatically-generated ADT testers and getters with `#`. - Removed syntax highlighting for solver variables. - Don't require periods after declarations and function definitions. - Print thread name during SMT debugging. - Make sure that the same SMT call is never made twice (with the same timeout). ### Fixed - Fixed bug with applying type substitutions that contain mappings to (possibly nested) type variables. - Updated name of formula type in Vim syntax file. - Fixed a couple bugs in SMT-LIB parser. - Fixed bug with missing case in unification algorithm. - Boolean operators now short circuit. - Reject programs that use top-down rewriting in combination with IDB predicates in the ML fragment. - Make sure that EDB relations are maintained during top-down rewriting, even when they are only referenced in the ML fragment. ## [0.1.0] - 2019-04-21 ### Added - Everything (initial release). ================================================ FILE: docs/Gemfile ================================================ source 'https://rubygems.org' gem "jekyll", "~> 4.3.4" # installed by `gem jekyll` # gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2 gem "just-the-docs", "0.10.0" # pinned to the current release # gem "just-the-docs" # always download the latest release ================================================ FILE: docs/_config.yml ================================================ title: Formulog description: Datalog for SMT-based static analysis theme: just-the-docs url: https://just-the-docs.github.io aux_links: GitHub Repository: https://github.com/HarvardPL/formulog ================================================ FILE: docs/eval_modes/compile.md ================================================ --- title: Compilation layout: page parent: Evaluation Modes nav_order: 3 --- # Compilation As an alternative to being directly interpreted (the default), Formulog programs can be compiled into a mix of C++ and Souffle code, which can then in turn be compiled into an efficient executable. To enable compilation, set the `--codegen` (`-c`) flag; generated code will be placed in the directory `./codegen/` (you can change this using the `--codegen-dir` option). Within this directory you can use `cmake` to compile the generated code into a binary named `flg`. For example, to compile and execute the `greeting.flg` program from above, you can use these steps: ``` java -jar formulog.jar -c examples/greeting.flg && \ cd codegen && \ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release && \ cmake --build build -j && \ ./build/flg --dump-idb ``` This should produce output like the following: ``` Parsing... Finished parsing (0.000s) Evaluating... Finished evaluating (0.029s) ==================== SELECTED IDB RELATIONS ==================== ---------- greeting (3) ---------- greeting("Hello, Bob") greeting("Hello, World") greeting("Hello, Alice") ``` Use the command `./build/flg -h` see options available when running the executable. For more information about the Formulog compiler, see the OOPSLA'24 paper [Making Formulog Fast: An Argument for Unconventional Datalog Evaluation](https://dl.acm.org/doi/10.1145/3689754) by Aaron Bembenek, Michael Greenberg, and Stephen Chong. ## Dependencies To build the generated code, you must have: - A C++ compiler that supports the C++17 standard (and OpenMP, if you want to produce a parallelized binary) - `cmake` (v3.21+) - [`boost`](https://www.boost.org/) (a version compatible with v1.81) - [`oneTBB`](https://oneapi-src.github.io/oneTBB/) (v2021.8.0 is known to work) - [`souffle`](https://souffle-lang.github.io/) (v2.3 is known to work; you have to use our [custom fork](https://github.com/aaronbembenek/souffle) if you want to combine compilation with [eager evaluation]({{ site.base_url }}{% link eval_modes/eager.md %}).) The Formulog Docker image already has these dependencies installed. ================================================ FILE: docs/eval_modes/eager.md ================================================ --- title: Eager Evaluation layout: page parent: Evaluation Modes nav_order: 4 --- # Eager Evaluation In addition to traditional semi-naive Datalog evaluation, Formulog supports _eager evaluation_, a novel concurrent evaluation algorithm for Datalog that is faster than semi-naive evaluation on some Formulog workloads (often because it induces a more favorable distribution of the SMT workload across SMT solvers). Whereas semi-naive evaluation batches derived tuples to process them in explicit rounds, eager evaluation eagerly pursues the consequences of each tuple as it is derived. Using eager evaluation with the Formulog interpreter is easy: just add the `--eager-eval` flag. Eager evaluation can also be used with the Formulog compiler, provided you install [our custom version of Souffle](https://github.com/aaronbembenek/souffle). When you configure `cmake` on the generated code, you need to add `-DFLG_EAGER_EVAL=On`. For example, to build a version of the greeting program that uses eager evaluation, use these commands: ``` java -jar formulog.jar -c examples/greeting.flg && \ cd codegen && \ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DFLG_EAGER_EVAL=On && \ cmake --build build -j && \ ./build/flg --dump-idb ``` For more information about eager evaluation, see the OOPSLA'24 paper [Making Formulog Fast: An Argument for Unconventional Datalog Evaluation](https://dl.acm.org/doi/10.1145/3689754) by Aaron Bembenek, Michael Greenberg, and Stephen Chong. ================================================ FILE: docs/eval_modes/index.md ================================================ --- title: Evaluation Modes layout: page nav_order: 4 --- # Evaluation Modes Given a Formulog program, there are many different ways to evaluate it. These pages describe the primary options supported by our current implementation of Formulog. ================================================ FILE: docs/eval_modes/options.md ================================================ --- title: Options and System Properties layout: page parent: Evaluation Modes nav_order: 1 --- # Options and System Properties Formulog evaluation is controlled by options and system properties. For example, to interpret the test program with SMT logging and 2 threads, use the `debugSmt` property and `-j 2` option: ``` java -DdebugSmt -jar formulog.jar example/greeting.flg -j 2 ``` ## Options Run Formulog with the `-h` flag to see a list of the command-line options that are currently available. As of Formulog v0.8.0, they are: ``` Usage: formulog [-chV] [--dump-all] [--dump-idb] [--dump-query] [--dump-sizes] [--eager-eval] [--smt-stats] [--codegen-dir=] [-D=] [-j=] [--smt-solver-mode=] [--dump=]... [-F=]... Runs Formulog. Formulog program file. -c, --codegen Compile the Formulog program. --codegen-dir= Directory for generated code (default: './codegen'). -D, --output-dir= Directory for .tsv output files (default: '.'). --dump= Print selected relations. --dump-all Print all relations. --dump-idb Print all IDB relations. --dump-query Print query result. --dump-sizes Print relation sizes. --eager-eval Use eager evaluation (instead of traditional semi-naive Datalog evaluation) -F, --fact-dir= Directory to look for fact .tsv files (default: '.'). -h, --help Show this help message and exit. -j, --parallelism= Number of threads to use. --smt-solver-mode= Strategy to use when interacting with external SMT solvers ('naive', 'push-pop', or 'check-sat-assuming'). --smt-stats Report basic statistics related to SMT solver usage. -V, --version Print version information and exit. ``` **Note:** Formulog does not print any results by default; use one of the `--dump*` options to print results to the console, or annotate intensional database (IDB) relations with `@disk` to dump them to disk. ## System Properties In addition to options, there are many system properties that can be set using the `-D` flag (as in `-DdebugSmt` or `-DuseDemandTransformation=false`). Some of the most useful ones are: * `debugSmt` - log debugging information related to SMT calls to the `solver_logs/` directory (defaults to false) * `debugMst` - print debugging information related to the magic set transformation (defaults to false) * `debugRounds` - print statistics for each round of seminaive evaluation (defaults to false) * `useDemandTransformation` - apply the demand transformation as a post-processing step after the magic set transformation (defaults to true) * `softExceptions` - ignore exceptions during evaluation (i.e., treat them as unification failures, and not as something that should stop evaluation; defaults to false) * `sequential` - run interpreter without a thread pool (helpful for debugging runtime; defaults to false) * `printRelSizes` - print final relation sizes (defaults to false) * `printFinalRules` - print the final, transformed rules (defaults to false) * `trackedRelations=REL_1,...,REL_n` - print facts from listed relations as they are derived (defaults to the empty list) * `smtLogic=LOGIC` - set the logic used by the external SMT solver (defaults to `ALL`) * `smtSolver=SOLVER` - set the external SMT solver to use; current options are `z3` (default), `cvc4`, `yices`, and `boolector` * `smtDeclareAdts` - whether to declare Formulog algebraic data types to the SMT solver upon initialization; set this to false for logics that do not support ADTs (defaults to true) ### Alternative SMT Solvers While we have primarily used Formulog with Z3 as the backend solver, we also have some experimental (not recently tested) support for other solvers; not all these solvers handle the full range of Formulog features. To use a solver besides Z3, you need to set the `smtSolver` system property (see above). For each solver, the relevant binary needs to be on your path: `z3` for Z3, `boolector` for Boolector, `cvc4` for CVC4, and `yices-smt2` for Yices. ================================================ FILE: docs/eval_modes/smt.md ================================================ --- title: Solver Modes and Incremental SMT Solving layout: page parent: Evaluation Modes nav_order: 2 --- # Solver Modes and Incremental SMT Solving The Formulog runtime associates one external SMT solver process per Formulog worker thread. Each SMT query is a list of conjuncts. If the SMT solver is invoked via the `is_sat_opt` or `get_model_opt` function, this list is explicitly given by the programmer; otherwise, if the solver is invoked via the `is_sat` or `is_valid` function, the Formulog runtime breaks the supplied proposition into conjuncts. Formulog supports three strategies for interacting with external SMT solvers; they can be set using the `--smt-solver-mode` option. Consider two SMT queries `x` and `y`, where `x` immediately precedes `y` and both are lists of conjuncts. - `naive`: reset the solver between queries `x` and `y` (do not use incremental SMT solving). - `push-pop`: try to use incremental SMT solving via the `push` and `pop` SMT commands. This can work well when query `y` extends query `x`; e.g., `y = c :: x`, where `c` is an additional conjunct; this situation most commonly occurs when using [eager evaluation]({{ site.base_url }}{% link eval_modes/eager.md %}). - `check-sat-assuming`: try to use incremental SMT solving via the `check-sat-assuming` SMT command. This caches conjuncts in the SMT solver in a way such that they can be enabled or disabled per SMT query, and works well if there are shared conjuncts between queries `x` and `y`, but query `x` is not simply an extension of query `y` (e.g., it omits a conjunct in query `y`). For more info, see the ICLP'20 extended abstract [Datalog-Based Systems Can Use Incremental SMT Solving](https://aaronbembenek.github.io/papers/datalog-incr-smt-iclp2020.pdf) by Aaron Bembenek, Michael Ballantyne, Michael Greenberg, and Nada Amin. ================================================ FILE: docs/index.md ================================================ --- title: Home layout: home nav_order: 1 --- # Welcome to Formulog! Formulog is a programming language that extends the logic programming language Datalog with SMT solving and first-order functional programming. Formulog was designed to be a domain-specific language for implementing SMT-based program analyses, like refinement type checking and symbolic execution. But who knows---maybe it will also have other uses! Questions? Feedback? Please [raise a GitHub issue](https://github.com/HarvardPL/formulog/issues/new)! ================================================ FILE: docs/lang_ref/goal_directed_eval.md ================================================ --- title: Goal-Directed Evaluation layout: page parent: Language Reference nav_order: 3 --- # Goal-Directed Evaluation By default, Formulog uses a standard bottom-up, saturating evaluation strategy. However, you can also trigger a form of goal-directed, top-down execution if you include a query in your Formulog program. A query is a rule with an empty head and a single (non-negated) body atom; for example, `:- p(X, "a").` is a query. Each program can only have a single query. The Formulog runtime will use the query to rewrite your program using (a variant of) the magic set transformation technique. The rewritten program simulates top-down evaluation when it is evaluated bottom-up. The rewriting happens after type checking, but before program validation (i.e., before the checks described in the "Program safety" section are run). This means that you are allowed to write "invalid" Formulog programs, so long as that when they are rewritten, they pass the validation checks. For example, this program is invalid evaluated bottom-up, since there are unbound variables (in the head of the rules): ``` rel member(i32, i32 list) member(X, X :: _Xs). member(X, _Y :: Xs) :- member(X, Xs). ``` However, when we add the query `:- member(_X, [1, 2, 3]).`, the program is rewritten to a valid program that can be evaluated bottom-up: ``` input_member_fb(Xs) :- sup_0_0(_0, Xs). input_member_fb([1, 2, 3]). member(X, X :: Xs) :- input_member_fb(X :: Xs). member(X, _0 :: Xs) :- sup_0_0(_0, Xs), member(X, Xs). sup_0_0(_0, Xs) :- input_member_fb(_0 :: Xs). ``` Our magic set transformation technique guarantees that if the original program meets the requirements of stratified negation, then the rewritten program will also be stratified. However, the transformation can turn a program that terminates into a program that does not terminate. Also, it cannot be used with predicates that are "invoked" from the functional fragment of Formulog. ## Query Syntax Queries are in the form `:- A.` where `A` is a positive (non-negated) atom. The typical rules about variable usage apply (see the "Anonymous variables" section of the [Program Safety page]({{ site.base_url }}{% link lang_ref/program_safety.md %})). If you want to have a query consisting of multiple atoms, write a rule defining a new relation and then query that relation. For example, the hypothetical query `:- A, B.` could be rewritten as the rule `R :- A, B.` and query `:- R.`. There can be only one query per program. ## Partial Goal-Directed Evaluation It is also possible to only use goal-directed evaluation for part of a program. You can control this by annotating IDB relation declarations with `@bottomup` and `@topdown`: ``` @bottomup rel foo(i32, string) @topdown rel bar(i32, i32, string) ``` A relation annotated as `@bottomup` will always be evaluated bottom-up (i.e., in an exhaustive fashion), and a relation annotated as `@topdown` will always be evaluated top-down (i.e., in a goal-directed fashion). These annotations can be used whether or not a top-level query is supplied, and there are no restrictions on how bottom-up and top-down relations can interact with each other (outside of the normal restriction of stratified negation). Furthermore, not every relation needs to be annotated. An unannotated relation will be evaluated bottom-up in the absence of a top-level query, and top-down otherwise. ================================================ FILE: docs/lang_ref/index.md ================================================ --- title: Language Reference layout: page nav_order: 5 --- # Language Reference This set of documents describes the Formulog language (as opposed to its current implementation). Please raise a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new) if anything is unclear, incomplete, or incorrect :) For an overview of Formulog and its motivations, we recommend checking out our [publications]({{ site.base_url }}{% link pubs.md %}). ================================================ FILE: docs/lang_ref/lang_basics.md ================================================ --- title: Language Basics layout: page parent: Language Reference nav_order: 1 --- # Language Basics Formulog is an extension of Datalog designed to support program analyses that use logical formulas, such as symbolic execution and refinement type checking. A Formulog program consists of three main components: * type definitions; * relation declarations and definitions; and * function definitions. ## Types Formulog has a strong, static type system. ### Built-in Types Formulog has five built-in primitive types: * booleans (`bool`), i.e., `true` and `false`; * signed 32-bit integers (`i32` or equivalently `bv[32]`), as in `42`; * signed 64-bit integers (`i64` or equivalently `bv[64]`), as in `42L`; * 32-bit floating point numbers (`fp32` or equivalently `fp[8,24]`), as in `42.0F`; * 64-bit floating point numbers (`fp64` or equivalently `fp[11,53]`), as in `42.0` or `42.0D`; and * string constants (`string`), as in `"hello"`. Beyond these primitive types, Formulog provides the following built-in algebraic data types: ``` type 'a list = | nil | cons('a, 'a list) type 'a option = | none | some('a) type cmp = | cmp_lt | cmp_eq | cmp_gt ``` It also has built-in types representing logical formulas, but a discussion of these is delayed until the [section on logical formulas]({{ site.base_url }}{% link lang_ref/logical_formulas.md %}). #### List Notation Formulog provides special notation for terms of the `list` type. The `cons` constructor can be written using the infix notation `::`; i.e., `X :: Y` is shorthand for `cons(X, Y)`. A list can also be written as a sequence of comma-separated elements between a pair of square brackets. For example, the term `[X, Y, Z]` is shorthand for `cons(X, cons(Y, cons(Z, nil)))`. The term `[]` is `nil`, the empty list. Both notations can be used in pattern matching (described below). ### User-Defined Types Formulog allows users to define their own (polymorphic) algebraic data types. For instance, this defines a list-like type: ``` type 'a my_list = | my_nil | my_cons('a, 'a my_list) ``` Like types, constructors must begin with a lowercase letter. Formulog also has support for tuple types, such as the type `i32 * string`, and users can also define type aliases, such as this one that defines a map to be an association list: ``` type ('k, 'v) map = ('k * 'v) my_list ``` Mutually recursive types are written using `and`: ``` type foo = | foo1 | foo2(bar) and bar = | bar1(foo) | bar2(bar, bar) ``` You can also define records, as here: ``` type 'a linked_list = { val : 'a; next : 'a linked_list option; } ``` Labels must be valid identifiers and cannot be shared across other types. For each label, Formulog will automatically generate a function with that name that extracts the relevant value from the record. For example, in the case of `linked_list`, Formulog will generate: ``` val : 'a linked_list -> 'a next : 'a linked_list -> 'a linked_list option ``` Formulog also supports OCaml-style functional record update, as in this example: ``` type point3d = { x : i32; y : i32; z : i32 } fun foo : i32 = let X = { x = 1; y = 2; z = 3 } in let Y = { X with x = -1; z = 0 } in x(Y) + y(Y) + z(Y) ``` A call `foo` would evaluate to the value `1`. ## Relations In Formulog, relations are declared using the keyword `rel`, followed by the name of the relation, and the types of the relation arguments. Relation arguments can also be given labels (as documentation): ``` rel foo(i32, string) rel pair(first: i32, second: i32) (* `first` and `second` are labels *) ``` Some relations are defined only in terms of explicitly enumerated facts. This one relates pairs of nodes and consists of three pairs: ``` type node = string rel edge(node, node) edge("a", "b"). edge("b", "c"). edge("c", "b"). ``` Relations like this—that consist only of enumerated facts—can be annotated with the `@edb` annotation, which tells Formulog to treat them as part of the extensional database (EDB). ``` @edb rel edge(node, node) ``` Formulog assumes that every relation not annotated with `@edb` is an intensional database (IDB) relation, meaning that it is defined by rules and should be treated as an output of the program. For instance, this predicate computes transitive closure over the previously defined `edge` predicate: ``` rel tc(node, node) tc(X, Y) :- edge(X, Y). tc(X, Z) :- tc(X, Y), edge(Y, Z). ``` A Formulog rule consists of a list of head atoms (the atoms to the left of the `:-`) and a list of body atoms (the atoms to the right of the `:-`). An atom is either a nullary predicate symbol (i.e., a predicate that takes no arguments) or a n-ary predicate symbol followed by a parenthesized, comma-separated list of terms. Each term is either * a primitive like `42`; * a variable like `X`; * a constructed term like `some(X :: [2, 3])`; * a term of the form `t not C`, where `t` is a term and `C` is a constructor symbol (this evaluates to `true` if the outermost constructor of `t` is not `C`); or * a function call to a user-defined or built-in function like `i32_to_i64(42)` (functions are described in the next section). Additionally, atoms in the body of a rule can be negated, as in the atom `!tc(X, "c")`. Restrictions on the use of negation will be described later in this guide. Formulog also has two built-in binary predicates, `=` and `!=`: ``` rel ok ok :- X = "hello", X != "goodbye". ``` The first of these predicate is true when its arguments unify to the same term, and the second is true when its arguments cannot be unified. Finally, any Formulog term of type `bool` can be used in place of an atom in the rule body, as here: ``` rel foo(bool) output p p :- foo(X), X. ``` where the rule is translated to ``` p :- foo(X), X = true. ``` ### Reading EDB Relations from Disk It is possible to specify that an EDB relation should be read from an external file by annotating its declaration with `@disk`, as in ``` @disk @edb foo(i32, i32, string list) @disk @edb bar(string) ``` The Formulog runtime will look in the directories specified on the command line for files called`foo.tsv` and `bar.tsv` (defaulting to the current directory). As suggested by the `.tsv` extension, these files should contain rows of tab-separated terms, where each row corresponds to one input fact, and each column corresponds to an argument position. So, a file `foo.tsv` might look like this ``` 42 0 ["x"] 24 1 ["", " "] 100 -1 [] ``` (note that the terms on a line are separated by tabs, *not* spaces); it would correspond to the facts ``` foo(42, 0, ["x"]). foo(24, 1, ["", " "]). foo(100, -1, []). ``` Similarly, a `bar.tsv` file looking like this ``` "hello" "goodbye" "ciao" "aloha" ``` would correspond to the facts ``` bar("hello"). bar("goodbye"). bar("ciao"). bar("aloha"). ``` Every fact directory must have a `.tsv` file for _every_ external input relation (the file can be empty). ### Writing IDB Relations to Disk An IDB relation can be annotated with the annotation `@disk`, in which case Formulog will dump its contents into a `.tsv` file in the directory specified on the command line (defaulting to the current directory). For example, the program ``` rel bar(i32, i32) bar(1, 2). bar(3, 4). @disk rel foo(i32, i32) foo(X, Y) :- bar(X, Y). ``` will result in a file `foo.tsv` with the tab-separated contents: ``` 1 2 3 4 ``` ## Functions Formulog allows users to define ML-style functions, that can then be invoked from within Datalog-style rules. These functions can be polymorphic, but cannot be higher-order. The functions must have explicit type annotations. For example, here is a function for finding the nth element of a list: ``` fun nth(Xs : 'a list, N : i32) : 'a option = match Xs with | [] => none | X :: Xs => if N < 0 then none else if N = 0 then some(X) else nth(Xs, N - 1) end ``` No special syntax is required for defining recursive functions, although mutually recursive functions must be defined with `and`, as here: ``` fun neg_abs(X: i32) = if X > 0 then -X else X fun is_even(X: i32) : bool = let X = neg_abs(X) in X = 0 || is_odd(X + 1) and is_odd(X: i32) : bool = let X = neg_abs(X) in X != 0 && is_even(X + 1) ``` We support some of the basic ML syntax constructions, like `match` and `let`. However, you will find Formulog's syntax to be less flexible than most ML implementations; for example, `some(X)` is okay but `some X` is not. Despite the fact that we do not support higher-order functions, we do support nested functions that can locally capture variables and we also support a special parameterized term `fold`: ``` fold[f] : ['a, 'b list] -> 'a ``` where `f` is the name of a function of type `['a, 'b] -> 'a`. Here's an example using both nested functions and `fold`: ``` fun rev(Xs: 'a list) : 'a list = let fun cons_wrapper(Xs: 'a list, X: 'a) : 'a list = X :: Xs in fold[cons_wrapper]([], Xs) ``` Top-level nullary functions (i.e., ones that take no arguments) can be introduced with the keyword `const` instead of `fun`: ``` const pi : fp64 = 3.14 (* same as `fun pi : fp64 = 3.14` *) ``` ### Lifted Relations and Aggregation Formulog allows any relation (i.e., EDB relations, IDB relations, and the built-in relations `!=` and `=`) to be lifted to a boolean-returning function. For instance, we can write code like this: ``` output bar(i32) fun foo(N:i32) : bool = bar(N + 1) ``` Here, the function `foo(n)` returns `true` whenever the `bar` relation contains `n + 1`. Formulog supports aggregation through the wild card term `??`, which can be used as an argument when "invoking" a relation as a function. For example, given the relation `p` that relates a `bool` to an `i32`, we have: * `p(true, 42)` returns a boolean (whether `true` is related to `42`) * `p(true, ??)` returns a list of `i32` terms (the ones that are related to `true`) * `p(??, 42)` returns a list of `bool` terms (the ones that are related to `42`) * `p(??, ??)` returns a list of pairs constituting the relation The use of lifted predicates must be stratified, as described in the "Program Safety" document. ### Built-in Functions Finally, Formulog already has a bunch of basic functions built-in (mostly to do with manipulating primitives): * functions for basic mathematical operations for types `i32`, `i64`, `fp32`, and `fp64`: * addition (`*_add`), as in `fp32_add` * subtraction (`*_sub`) * multiplication (`*_mul`) * negation (`*_neg`) * bit vector operations for types `i32` and `i64`: * bitwise and (`*_and`), * bitwise or (`*_or`) * bitwise exclusive or (`*_xor`), for types `i32` and `i64`; * signed division (`*_sdiv`) * signed remainder (`*_srem`) * unsigned division (`*_udiv`) * unsigned remainder (`*_urem`) * shift left (`*_shl`) * logical shift right (`*_lshr`) * arithmetic shift right (`*_ashr`) * float operations for types `fp32` and `fp64`: * equality (`*_eq`; this is floating point equality, as opposed to structural equality via the predicate `=`); * division (`*_div`) * remainder (`*_rem`) * Comparison operations `*_lt`, `*_le`, `*_gt`, `*_ge` for types `i32`, `i64`, `fp32`, and `fp64` (the bit vector ones are implicitly for signed comparison) * Signed compare (`*_scmp`) and unsigned compare (`*_ucmp`) operators for types `i32` and `i64`; these return a term of type `cmp` (described above) * boolean operators `!`, `&&`, and `||` * numeric primitive conversion operations, in the form `*_to_*` (e.g., `i32_to_fp64`) * the operators `string_to_i32` and `string_to_i64`, which convert the string representation of an integer to a term of type `i32 option` and `i64 option`, respectively. The string should either be a decimal integer preceded optionally by `-` or `+`, or a hexadecimal integer preceded by `0x`. The operations return `none` if the string is not in the proper format or represents an integer of too great magnitude to fit in 32/64 bits. Standard arithmetic notation can be used for signed `i32` operations. For example, `38 + 12 / 3` is shorthand for `i32_add(38, i32_sdiv(12, 3))`. Formulog supplies some `string` manipulation functions: ``` string_concat : [string, string] -> string string_cmp : [string, string] -> cmp string_matches : [string, string] -> bool string_starts_with : [string, string] -> bool substring : [string, i32, i32] -> string option string_length : [string] -> i32 char_at : [string] -> i32 option string_to_list : [string] -> i32 list list_to_string : [i32 list] -> string to_string : 'a -> string ``` The function `string_matches` returns `true` when its first argument matches its second argument, which can be a regular expression. The function `string_starts_with` returns `true` when its second argument is a prefix of its first argument. The function `substring(s, i, j)` extracts the substring of `s` starting at index `i` and ending at index `j - 1`; it returns `none` for inappropriate `i` or `j`. The functions `char_at`, `string_to_list`, and `list_to_string` can be used to treat a string as a list of characters. We represent characters as terms of type `i32`. We currently do not guarantee any particular behavior if an integer less than 0 or greater than 255 is used as a character. Finally, for the purposes of debugging, Formulog supplies a `print` function of type `'a -> bool`; it always evaluates to `true`. ================================================ FILE: docs/lang_ref/logical_formulas.md ================================================ --- title: Logical Formulas layout: page parent: Language Reference nav_order: 4 --- # Logical Formulas Formulog provides support for representing and reasoning about logical formulas. ## Example This example program would produce the facts `ok1`, `ok2`, and `ok3`. ``` rel ok1 ok1 :- is_valid(`false ==> true`), !is_sat(`true ==> false`). rel ok2 ok2 :- E = `bv_add(#x[bv[32]], 42) #= 0`, is_sat(E), F = `bv_sge(#x[bv[32]], 0)`, is_sat(F), !is_sat(`E /\ F`). type 'a my_list = | my_nil | my_cons('a, 'a my_list) rel ok3 ok3 :- Xs = #xs[bool my_list], Ys = #ys[bool my_list], E = `Xs #= my_cons(true, Ys)`, is_sat(E), F = `#is_my_nil(Xs)`, is_sat(F), !is_sat(`E /\ F`). ``` ## Formula Types For every non-formula type τ, there are two corresponding types that are used to represent τ-valued logical formulas. The first is `τ smt`, which represents a τ-valued formula. The second is `τ sym`, which represents a τ-valued formula variable (it is sometimes helpful to distinguish between these two types). You will often see formulas quoted with backticks, as in ``` `#x[bool] #= true` ``` Quoted terms are type checked differently than terms outside of quotations. Outside of quotations, the types τ, `τ smt`, and `τ sym` are treated as being distinct. However, in quoted terms, they are treated as being all the same type. This bimodal type checking makes it easy to write expressive formulas, while making sure that evaluation outside of formulas does not get stuck, which might happen if a boolean formula were passed to a function expecting a concrete boolean argument. The Formulog type sensitive is flow-sensitive, in that the order of the atoms and terms in a rule affect whether that rule is considered well typed or not. For example, it rejects the first rule in this program and not the second, even though they are logically equivalent: ``` rel p(bool smt) rel q(bool) rel not_ok rel ok not_ok :- p(`X`), q(X). ok :- q(X), p(`X`). ``` It rejects the first rule because, given a left-to-right reading of the rule, `X` is bound in a position that has type `bool smt`, and so there is no guarantee it is a concrete `bool` (which would be required for it to be a member of `q`). The second rule is fine since `X` is bound in a position that has type `bool`, which becomes a `bool smt` when it is quoted as an argument to `p`. The type checker currently uses the order that the rule was originally written; in the future, the type checker could try to reorder rules to make them well typed. ### Uninterpreted Sorts Formulog allows users to define uninterpreted sorts that can be used within formulas. For instance, you can declare a polymorphic uninterpreted sort like this: ``` uninterpreted sort ('a, 'b) foo ``` ## Representing Formulas Formulas are constructed from a library of constructors and are typically quoted by backticks, which tells the type checker to use the "formula mode" of the bimodal type system. Function calls that take arguments cannot appear within quotations. ### Formula variables Formulog distinguishes between logic programming variables (like `X`) and formula variables (like `#x[bool]`). The latter are only interpreted as variables within formula; otherwise they are ground terms. Formula variables can be created using a special syntax: a pound sign, followed by a term `t` of arbitrary type within curly braces, followed by a type τ within square brackets, as in ``` #{t}[τ] ``` τ cannot have any type variables and cannot contain any formula types (like `sym` or `smt`). The term `t` is the "name" of the formula variable. A formula variable will unify with another formula variable only if they have the same "name" and type. For example, ``` #{42}[bool] = #{"hello"}[bool] ``` never holds, although the formula ``` `#{42}[bool] #= #{"hello"}[bool]` ``` is satisfiable (where `#=` is the notation for formula equality). **Note:** A formula variable `#{t}[τ]` is guaranteed to not unify with any subterm of `t`; that is, it is fresh for `t`. There is also a shorthand syntax: a pound sign, followed by an identifier, followed by a type within square brackets, as in `#x[bool]`. This is the same thing as `#{"x"}[bool]`. ### Built-in Formula Terms Formulog provides built-in terms that are used to construct formulas that should be interpreted under a particular theory. For the most part, these constructors directly reflect the SMT-LIB standard. #### Parameterized Terms You will see that many formula terms are parameterized with a type or natural number. For example, the constructor for formula equality, `smt_eq[τ]`, is parameterized with the type τ of the operands of the equality, and the constructor for a bit vector constant, `bv_const[k]`, is parameterized with the width `k` of the bit vector. These parameters are necessary either because the type information is important to have at runtime (when serializing formulas into SMT-LIB), or for type safety reasons (issues arise if the type of the term does not uniquely determine the types of its arguments). However, Formulog can often infer the correct type without an explicit annotation. If you leave out the square brackets, Formulog will try to infer every parameter for that term; alternatively, select parameters can be inferred by using the wildcard parameter `?`. For example, these formulas all say the same thing: ``` `smt_eq[bv[32]](bv_const[32](42), 0)` `smt_eq[?](bv_const[?](42), 0)` `smt_eq(bv_const(42), 0)` ``` Formulog can infer that this is a comparison of 32-bit bit vectors from the fact that the second operand is the constant `0`, which has type `bv[32]`. However, the following formulas are unacceptable, since Formulog cannot infer the widths of the bit vectors in the comparisons: ``` `smt_eq[?](bv_const[?](42), bv_const[?](0))` `smt_eq(bv_const(42), bv_const(0))` ``` In this case, one annotation is enough to clarify things: ``` `smt_eq[bv[32]](bv_const(42), bv_const(0))` `smt_eq(bv_const[32](42), bv_const(0))` ``` The parameters to a parameterized constructor have to be fully resolved at compile time. #### Logical Connectives Formulog has the standard first-order logic connectives: ``` smt_not : bool smt -> bool smt smt_eq[τ] : [τ smt, τ smt] -> bool smt smt_and : [bool smt, bool smt] -> bool smt smt_or : [bool smt, bool smt] -> bool smt smt_imp : [bool smt, bool smt] -> bool smt smt_ite : [bool smt, 'a smt, 'a smt] -> 'a smt smt_let[τ] : [τ sym, τ smt, 'a smt] -> 'a smt smt_exists : [smt_wrapped_var list, bool smt, smt_pattern list list] -> bool smt smt_forall : [smt_wrapped_var list, bool smt, smt_pattern list list] -> bool smt ``` The quantifiers deserve some explanation. The first argument is a list of "wrapped" formula variables bound by the quantifier; the type `smt_wrapped_var` has a single constructor: ``` smt_wrap_var[τ] : τ sym -> smt_wrapped_var ``` The second argument is the body of the quantifier. The third and final argument represents a list of patterns to supply for trigger-based quantifier instantiation. Each member of the outermost list represents a single pattern, possibly consisting of multiple terms. The type `smt_pattern` has a single constructor: ``` smt_pat[τ] : τ -> smt_pattern ``` However, it is unlikely that you will have to use these constructors directly, as we supply notation that should cover most situations: ``` ~ ... (* negation *) ... #= ... (* equality *) ... /\ ... (* conjunction *) ... \/ ... (* disjunction *) ... ==> ... (* implication *) ... <==> ... (* iff *) #if ... then ... else ... #let ... = ... in ... ``` The binary operators are listed above in order of precedence (so `#=` binds the most tightly, and `<==>` the least tightly). They all associate to the right, except for `#=`, which associates to the left. There is also notation for the quantifiers `forall` and `exists`, as in this example: ``` `forall #x[bool]. exists #y[bool]. #x[bool] #= ~#y[bool]` ``` The notation supports binding multiple variables: ``` `exists #x[bool], #y[bool]. #x[bool] #= ~#y[bool]` ``` You can also specify patterns for trigger-based instantiation. For example, say that `f` is an uninterpreted function from bools to bools. This formula says that `f(x) = x` for all `x`, using `f(x)` as a trigger: ``` `forall #x[bool] : f(#x[bool]). f(#x[bool]) #= #x[bool]` ``` The notation supports patterns with multiple terms (they should be separated by commas); however, it does not support multiple patterns, in which case you need to use the explicit constructor described above. #### Bit Vectors We have bit vectors, where `bv[k] smt` is a `k`-bit symbolic bit vector: ``` bv_const[k] : bv[32] -> bv[k] smt bv_big_const[k] : bv[64] -> bv[k] smt bv_neg : bv[k] smt -> bv[k] smt bv_add : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_sub : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_mul : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_sdiv : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_srem : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_and : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_or : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_xor : [bv[k] smt, bv[k] smt] -> bv[k] smt bv_to_bv_signed[j,k] : bv[j] smt -> bv[k] smt bv_to_bv_unsigned[j,k] : bv[j] smt -> bv[k] smt fp_to_sbv[i,j,k] : fp[i,j] smt -> bv[k] smt fp_to_ubv[i,j,k] : fp[i,j] smt -> bv[k] smt bv_slt[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_sle[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_sgt[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_sge[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_ult[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_ule[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_ugt[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_uge[k] : [bv[k] smt, bv[k] smt] -> bool smt bv_extract[j,k] : [bv[j] smt, i32, i32] -> bv[k] smt bv_concat[i,j,k] : [bv[i] smt, bv[j] smt] -> bv[k] smt ``` Note that in some cases the bit vector width is a parameter to the constructor; as noted previously, parameters can often be inferred. The `bv_extract` and `bv_concat` constructors currently do not enforce some constraints on their parameters (for example, with `bv_concat[i,j,k]`, it must be that `i + j = k`). Illegal parameter choices are therefore not caught by the type system, and might lead to SMT solver crashes at runtime. #### Floating Point And we have floating point, where `fp[j,k] smt` is a symbolic floating point number with exponent length `j` and signficand length `k`: ``` fp_const[j,k] : fp[32] -> fp[j,k] smt fp_big_const[j,k] : fp[64] -> fp[j,k] smt fp_neg : fp[j,k] smt -> fp[j,k] smt fp_add : [fp[j,k] smt, fp[j,k] smt] -> fp[j,k] smt fp_sub : [fp[j,k] smt, fp[j,k] smt] -> fp[j,k] smt fp_mul : [fp[j,k] smt, fp[j,k] smt] -> fp[j,k] smt fp_div : [fp[j,k] smt, fp[j,k] smt] -> fp[j,k] smt fp_rem : [fp[j,k] smt, fp[j,k] smt] -> fp[j,k] smt fp_to_fp[h,i,j,k] : fp[h,i] smt -> fp[j,k] smt bv_to_fp[i,j,k] : bv[i] smt -> fp[j, k] smt fp_lt[j,k] : [fp[j,k] smt, fp[j,k] smt] -> bool smt fp_le[j,k] : [fp[j,k] smt, fp[j,k] smt] -> bool smt fp_gt[j,k] : [fp[j,k] smt, fp[j,k] smt] -> bool smt fp_ge[j,k] : [fp[j,k] smt, fp[j,k] smt] -> bool smt fp_is_nan[j,k] : fp[j,k] smt -> bool smt fp_eq[j,k] : [fp[j,k] smt, fp[j,k] smt] -> bool smt ``` To make it syntactically more pleasant to deal with common floating point types, instead of supplying both the exponent and significand length, users can supply a single parameter that is expanded into the appropriate exponent and significand: * the parameter `16` is expanded to `5,11`; * the parameter `32` is expanded to `8,24`; * the parameter `64` is expanded to `11,53`; and * the parameter `128` is expanded to `15,113`. For example, the term `fp_const[32](...)` is equivalent to `fp_const[8,24](...)`. #### Integers Mathematical integers are represented by the type `int smt`: ``` int_abs : int smt -> int smt int_neg : int smt -> int smt int_const : bv[32] -> int smt int_big_const : bv[64] -> int smt int_ge : [int smt, int smt] -> bool smt int_gt : [int smt, int smt] -> bool smt int_le : [int smt, int smt] -> bool smt int_lt : [int smt, int smt] -> bool smt int_add : [int smt, int smt] -> int smt int_mul : [int smt, int smt] -> int smt int_mod : [int smt, int smt] -> int smt int_sub : [int smt, int smt] -> int smt int_div : [int smt, int smt] -> int smt int_to_bv[k] : int smt -> bv[k] smt bv_to_int[k] : bv[k] smt -> int smt ``` Note that `int_to_bv[k]` and `bv_to_int[k]` do not correspond to any operations in the SMT-LIB standard. These are serialized as `int2bv` and `bv2int` operations, which are supported by Z3, but not by some other solvers. #### Arrays Arrays from `'a` to `'b` are represented by the type `('a, 'b) array smt`: ``` array_select[τ] : [(τ, 'a) array smt, τ smt] -> 'a smt array_store : [('a, 'b) array smt, 'a smt, 'b smt] -> ('a, 'b) array smt array_default[τ] : (τ, 'a) array smt -> 'a smt array_const : 'b smt -> ('a, 'b) array smt ``` #### Strings Symbolic strings are represented by the type `string smt`: ``` str_at : string smt, int smt -> string smt str_concat : string smt, string smt -> string smt str_contains : string smt, string smt -> bool smt str_indexof : string smt, string smt, int smt -> int smt str_len : string smt -> int smt str_prefixof : string smt, string smt -> bool smt str_replace : string smt, string smt, string smt -> string smt str_substr : string smt, int smt, int smt -> string smt str_suffixof : string smt, string smt -> bool smt ``` These operations follow the theory of strings supported by Z3 and CVC4. ### Using Algebraic Data Types and Records in Formulas Terms constructed from user-defined algebraic data types can also be used in formulas, where they are interpreted under the theory of algebraic data types. Say we define this type: ``` type 'a my_list = | my_nil | my_cons('a, 'a my_list) ``` In addition to being able to use the constructors `my_nil` and `my_cons` within a formula, one can also use constructors that are automatically generated by Formulog and that make it easier to state formulas about `my_list` terms: ``` #is_my_nil : 'a my_list smt -> bool smt #is_my_cons : 'a my_list smt -> bool smt #my_cons_1 : 'a my_list smt -> 'a smt #my_cons_2 : 'a my_list smt -> 'a my_list smt ``` These automatically generated constructors fall into two categories: testers, which are identified by the prefix `#is_` followed by the name of the constructor and are used to test the outermost constructor of a (possibly symbolic) term, and getters, which are identified by the name of the constructor prefixed with a `#` and followed by an underscore and an argument position, and which are used to extract the argument of a (possibly symbolic) term. Symbolic getters are also created for records, where each getter is the name of the relevant label prefixed with `#`. ### Uninterpreted Functions Formulog also provides a way to declare uninterpreted functions, as here: ``` uninterpreted fun foo(bv[32] smt) : bool smt ``` This effectively defines a new constructor for `bool smt` that expects a single argument of type `bv[32] smt`. Uninterpreted functions can only be used within formulas. ## Reasoning about Formulas Formulog currently provides these functions for reasoning about/manipulating formulas: ``` is_sat : bool smt -> bool is_sat_opt : [bool smt list, i32 option] -> bool option is_valid : bool smt -> bool get_model : [bool smt list, i32 option] -> model option query_model : ['a sym, model] -> 'a option ``` The functions `is_sat` and `is_valid` check the satisfiability and validity, resp., of their argument and throw an exception if the SMT solver returns `unknown`. The function `is_sat_opt` takes a list of propositions and checks for the satisfiability of their conjunction, returning `none` in the case of `unknown`; it also takes an argument for an optional timeout. The function `get_model` takes a list of propositions and an optional timeout and returns a model for the conjunction of those propositions if the SMT solver finds one in time; it returns `none` otherwise. Variables in a model can be inspected using `query_model`, which will return `none` if a variable is not present in the model or if it is of a type that cannot be concretely represented in Formulog (for example, Formulog does not have a concrete representation of a 13-bit vector). ================================================ FILE: docs/lang_ref/program_safety.md ================================================ --- title: Program Safety layout: page parent: Language Reference nav_order: 2 --- # Program Safety Formulog imposes some restrictions on programs so that it can give guarantees about runtime behavior. Beyond the type system, these restrictions fall into two categories: restrictions on variable usage and restrictions on negation. ## Variable Usage Correct variable usage is a tricky aspect of logic programming; this section describes Formulog's restrictions on this front. ### Anonymous Variables To help catch bugs related to variable usage, Formulog requires that every variable that does not start with an underscore occurs more than once in its scope. Every variable that begins with an underscore is treated as "anonymous," in that every occurrence of that variable represents a distinct variable. For this reason, Formulog does not allow any variable that begins with an underscore to occur more than once in a scope, except for the traditional anonymous variable `_`. ### Binding Variables Formulog requires that every variable in a rule is "bound." In what follows, we use the identifiers `p` for a relation, `c` for a constructor, and `f` for a function. A variable is bound when: * it is explicitly unified via the `=` built-in predicate with a term that does not contain any unbound variables (e.g., `X` and `Y` are bound via the atom `(X, 0) = (42, Y)`); * it is an anonymous variable that occurs as a top-level argument to a negated atom (e.g., `_X` is considered bound in `!p(_X)`); or * it occurs in the argument to a positive atom in the rule body, and that occurrence is not also the argument to a function call. For example, the occurrence of `X` in the atom `p(c(X))` is bound, but it is not bound in the atom `p(f(X))`. Furthemore, the ML fragment of Formulog is evaluated using call-by-value semantics, which means that the arguments to a function need to be normalized and ground (i.e., variable-free) before a call site can be evaluated. Consequently, every variable occurring as an argument to a function must be bound going into the call site. To check whether these variable binding conditions are met, the Formulog runtime currently performs a single pass over the rule, going from left to right across the rule body, and then finishing on the rule head. This means that the order in which variables are bound affects whether the Formulog runtime accepts a rule or not, even though different orders might be logically equivalent. For example, this rule is disallowed, because the call to `f(X)` occurs syntactically before the binding of `X` (given a left-to-right reading of the rule): ``` not_ok :- p(f(X)), p(X). ``` This rule would be accepted if it were rewritten as: ``` ok :- p(X), p(f(X)). ``` Similarly, the checker is overly conservative in its treatment of the `=` predicate, rejecting rules that require "sub-unifications" to occur in an order that is not left to right: ``` not_ok :- (_, X) = (f(X), 42). ``` Here, the second argument of the tuple needs to be unified before the first argument can be. An equivalent rule would be accepted: ``` ok :- X = 42, _ = f(X). ``` In theory, Formulog could rewrite rules in situations like these to make it easier to meet the binding requirements. It currently does not do so because type checking is flow-sensitive, and so the runtime would need to ensure that any rewriting results in a rule that is still well-typed. Although we have not found our current approach to be a hindrance in practice, this is something that we could implement in the future. ## Negation, Aggregation, and Stratification To ensure that every program has a least model, Formulog requires the use of stratified negation and aggregation, a common restriction in Datalog. Intuitively, this restriction ensures that there are no recursive negation/aggregation dependencies between predicates. Since Formulog allows predicate symbols to appear in function bodies, determining whether a Formulog program is stratified is slightly more complicated than in the Datalog case. To determine if a program is stratified we construct a dependence graph, where each node represents a different predicate. There is a "positive" edge from a predicate `p` to a predicate `q` if `p` appears in a positive atom in the body of a rule defining `q`. There is a "negative" edge from `p` to `q` if `p` appears in a negated atom in the body of a rule defining `q`, or `p` appears in the body of an expression that may be transitively invoked from a rule defining `q`. A program is stratified if there are no cycles in the dependence graph that include a "negative" edge. ================================================ FILE: docs/pubs.md ================================================ --- title: Publications and Artifacts layout: page nav_order: 6 --- # Publications and Artifacts Check out our academic papers to learn more about the science behind Formulog. ## Formulog Language Design Our [OOPSLA'20 paper](https://dl.acm.org/doi/10.1145/3428209) introduces Formulog, motivates its language design, and demonstrates its use through three extensive case studies. To cite this paper, please use an entry like this one: ``` @article{Bembenek2020Formulog, author = {Aaron Bembenek and Michael Greenberg and Stephen Chong}, title = {Formulog: {D}atalog for {SMT}-{B}ased {S}tatic {A}nalysis}, journal = {Proceedings of the {ACM} on Programming Languages}, year = {2020}, volume = {4}, number = {OOPSLA}, doi = {10.1145/3428209}, pages = {141:1--141:31} } ``` The refereed artifact for this paper is [available on Zenodo](https://zenodo.org/records/4039122). ## Formulog Performance Our [OOPSLA'24 paper](https://dl.acm.org/doi/10.1145/3689754) describes speeding up Formulog via 1) compilation to Soufflé and 2) eager evaluation, a novel Datalog evaluation strategy that works well for some SMT-heavy workloads. To cite this paper, please use an entry like this one: ``` @article{Bembenek2024Making, author = {Aaron Bembenek and Michael Greenberg and Stephen Chong}, title = {Making {F}ormulog {F}ast: An {A}rgument for {U}nconventional {D}atalog {E}valuation}, journal = {Proceedings of the {ACM} on Programming Languages}, year = {2024}, volume = {4}, number = {OOPSLA2}, doi = {10.1145/3689754}, pages = {314:1--314:30} } ``` The refereed artifact for this paper won a [distinguished artifact](https://2024.splashcon.org/track/splash-2024-oopsla-artifacts#distinguished-artifacts) award; it is [available on Zenodo](https://zenodo.org/records/13372573). ## Short Papers For more information on how Formulog interfaces with external SMT solvers, see the ICLP'20 extended abstract [Datalog-Based Systems Can Use Incremental SMT Solving](https://arxiv.org/html/2009.09158v1/#EPTCS325.7) by Aaron Bembenek, Michael Ballantyne, Michael Greenberg, and Nada Amin ([PDF available here](https://aaronbembenek.github.io/papers/datalog-incr-smt-iclp2020.pdf)). For an introduction to Formulog geared towards an audience already well versed in Datalog, see the Datalog 2.0'22 short paper [Formulog: Datalog + SMT + FP](https://ceur-ws.org/Vol-3203/short2.pdf) by Aaron Bembenek, Michael Greenberg, and Stephen Chong. ================================================ FILE: docs/starting.md ================================================ --- title: Getting Started layout: page nav_order: 2 --- # Getting Started Thank you for your interest in Formulog! This page describes how to set up Formulog and provides some pointers on writing Formulog programs. ## Setting up Formulog There are three main ways to set up Formulog (listed in increasing order of number of dependencies): - Using the Docker image - Downloading the JAR - Building from source ### Use the Docker image Prebuilt images are available on [Docker Hub](https://hub.docker.com/r/aaronbembenek/formulog/tags). If you have Docker installed, you can spin up an Ubuntu container with Formulog, our custom version of Soufflé, and some example programs by running this command (replace `X.Y.Z` with the latest version): ```bash docker run -it aaronbembenek/formulog:X.Y.Z # may require sudo ``` This should place you in the directory `/root/formulog/`, with a file `formulog.jar` and some example Formulog programs in the `examples/` directory. ### Download the JAR Dependencies: - JRE 11+ - Z3 (v4.12.2 is known to work; other recent versions should work too) You can find a prepackaged JAR file in the [Releases section](https://github.com/HarvardPL/formulog/releases) of the GitHub repository. ### Build from Source Dependencies: - JDK 11+ - Maven (v3.9.5 is known to work) - Z3 (v4.12.2 is known to work; other recent versions should work too) To build an executable JAR, run the command `mvn package` from the project source directory. This will create an executable JAR with a name like `formulog-X.Y.Z-SNAPSHOT-jar-with-dependencies.jar` in the `target/` directory. If `mvn package` fails during testing, it might mean that there is a problem connecting with Z3. You can compile without testing by adding the `-DskipTests` flag. ### Test Your Setup If you have set up Formulog, you should now have an executable JAR at your fingertips. The JAR expects a single Formulog file as an argument. Let's test it on the following Formulog program (available in the Docker image and the base repository directory as `examples/greeting.flg`): ``` @edb rel entity(string) entity("Alice"). entity("Bob"). entity("World"). rel greeting(string) greeting(Y) :- entity(X), some(M) = get_model([`#y[string] #= str_concat("Hello, ", X)`], none), some(Y) = query_model(#y[string], M). ``` Run the following command (where `formulog.jar` is replaced with the name of the executable JAR): ``` java -jar formulog.jar examples/greeting.flg --dump-idb ``` You should get results like these, indicating that three `greeting` facts have been derived: ``` Parsing... Finished parsing (0.202s) Type checking... Finished type checking (0.024s) Rewriting and validating... Finished rewriting and validating (0.253s) Evaluating... Finished evaluating (0.354s) ==================== SELECTED IDB RELATIONS ==================== ---------- greeting (3) ---------- greeting("Hello, Alice") greeting("Hello, Bob") greeting("Hello, World") ``` ## Writing Formulog Programs Now that you have Formulog set up, the fun part starts: writing Formulog programs! Check out our [tutorial]({{ site.base_url }}{% link tutorial/index.md %}) for a walk-through of how to encode a refinement type system in Formulog. Additional short-ish example programs can be found in the `examples/` directory (in the Docker image or repository base directory). For examples of larger developments, see the case studies we have used in publications: - [a refinement type checker](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/dminor/bench.flg) - [a bottom-up points-to analysis for Java](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/scuba/bench.flg) - [a symbolic executor an LLVM fragment](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/symex/bench.flg) See the [language reference]({{ site.base_url }}{% link lang_ref/index.md %}) for details about Formulog constructs. Syntax highlighting is available for Visual Studio Code (follow instructions [here](https://github.com/HarvardPL/formulog-syntax)) and Vim (install [misc/flg.vim](https://github.com/HarvardPL/formulog/blob/master/misc/flg.vim)). Finally, please raise a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new) if you want to try out Formulog but would like additional information or assistance---we're happy to help! :) ================================================ FILE: docs/tutorial/index.md ================================================ --- title: Tutorial layout: page nav_order: 3 --- # Tutorial: Building a Refinement Type Checker In this tutorial, we'll implement a type checker for a small (but still interesting) refinement type system in Formulog. In particular, we'll implement the declarative, bidirectional type checking rules for the first system in the article [Refinement Types: A Tutorial](https://arxiv.org/abs/2010.07763v1) by Ranjit Jhala and Niki Vazou [1]. Our hope is that our tutorial gives a good overview of many Formulog features, and a flavor of what it is like to program a nontrivial analysis in Formulog. ### Intended Audience This tutorial is intended for the PL practitioner (e.g., a grad student, academic, or research engineer). We assume you are familiar with SMT solving, ML-like functional languages, and logic programming, and also have some level of comfort with formal programming language notation (like inference rules). It is also probably helpful to have read one of our [Formulog publications]({{ site.base_url }}{% link pubs.md %}), which should (hopefully) give a good sense for the overall design of the language and its motivations. If you are not familiar with refinement type checking or bidirectional type systems, you should probably skim the first few sections of the tutorial by Jhala and Vazou [1] (we'll focus on Sections 3.1 and 3.2). ### Help Improve This Tutorial We're eager for feedback on ways to make this tutorial clearer and more useful! If you have a question about the content of this tutorial, a suggestion for improvement, or a bug report, please let us know by raising a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new). ### Attribution This tutorial includes figures from the article [Refinement Types: A Tutorial](https://arxiv.org/abs/2010.07763v1) by Ranjit Jhala and Niki Vazou [1], which has been published under a [CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0/). We will refer to this article as "JV" for short. ## General Approach One of the advantages of writing a program analysis in Datalog is to be able to implement analysis logic at the level of mathematical specification. Formulog extends this benefit to analyses where the formal specification involves constructing logical terms (i.e., SMT formulas) and determining their satisfiability/validity (i.e., SMT solving). Our typical approach when implementing an analysis in Formulog is thus to try to directly translate the formal specification of an analysis---inference rules and mathematical helper functions---into Horn clauses and functional code. This is the approach we will follow in this tutorial: directly translate the formalism of JV as we encounter it, and then go back to patch our implementation as necessary. Concretely, we will work our way through JV Sections 3.1 and 3.2. For the full, final code, see [tutorial.flg](https://github.com/HarvardPL/formulog/blob/master/examples/tutorial.flg). ## Definitions The first formalism we encounter in JV Section 3.1 is a definition of types and environments (Figure 3.1), which also refers to the definitions of predicates (Figure 2.1). ![Figure 3.1](./images/figure_3_1.png) ![Figure 2.1](./images/figure_2_1.png) We can encode these BNF-style definitions (along with the definition of constraints) using Formulog's support for algebraic data types: ``` (* An algebraic data type with a single variant *) type basic_typ = | b_int (* A type alias *) type var = string (* Interpreted operations: for simplicity, we'll support just a few *) type op = | o_add | o_eq type pred = | p_var(var) | p_bool(bool) | p_int(i32) (* i32 is the type of a 32-bit signed integer *) | p_interp_op(op, pred, pred) | p_conj(pred, pred) | p_disj(pred, pred) | p_neg(pred) | p_ite(pred, pred, pred) type constraint = | c_pred(pred) | c_conj(constraint, constraint) | c_imp(var, basic_type, pred, constraint) type typ = | t_refined(basic_typ, var, pred) | t_func(var, typ, typ) type kind = | k_base | k_star (* Tuples and lists are built-ins *) type env = (var * typ) list ``` We can then similarly encode expressions, following Figure 3.2. ![Figure 3.2](./images/figure_3_2.png) ``` type expr = | e_int(i32) | e_var(var) | e_let(var, expr, expr) | e_lambda(var, expr) | e_app(expr, var) | e_annot(expr, typ) ``` ### Well-formedness The first judgments---which define type well-formedness---are given in Figure 3.3. ![Figure 3.3](./images/figure_3_3.png) Typically, in Formulog, you would encode inference rules like these using Horn clauses, so let's do that here. First, we need to declare a relation for well-formedness: ``` rel wf(env, typ, kind) ``` We can then encode the rules one by one, with reference to this relation. Let's start with the simplest rule, Wf-Kind: ``` wf(G, T, k_star) :- wf(G, T, k_base). ``` Horn clauses are implications (read right to left), so this rule is saying that types that are well-formed at kind base are also well-formed at kind star. Identifiers beginning with an uppercase letter are logic programming variables, which are implicitly universally quantified across the entire rule. Wf-Fun is not too bad to encode either: ``` wf(G, t_func(X, S, T), k_star) :- wf(G, S, _Ks), wf((X, S) :: G, T, _Kt). ``` Two things to note: - Identifiers that begin with an underscore are anonymous variables. Formulog will reject rules where a non-anonymous variable only appears once (a common bug). - As in ML variants, the infix constructor `::` is the cons of a value and a list; in this case, we use it to extend the environment with a new binding (represented as a tuple). Once we get to WF-Base, we notice that we're missing something: the premise requires the well-sortedness of a predicate, a judgment that is not defined by JV, although the authors say that this amounts to standard type checking (with refinements ignored). We'll have to declare a relation for this and encode the rules: ``` rel pred_wf(env, pred, basic_typ) ``` First, we might try to encode that a boolean value is, well, a boolean; to do so, we need to revise our definition of basic types to also include booleans: ``` (* Revised definition *) type basic_typ = | b_int | b_bool ``` We can then encode the rule for boolean literals: ``` pred_wf(_G, p_bool(_B), b_bool). ``` The rules are straightforward for most of the other constructs: ``` pred_wf(_G, p_int(_I), b_int). pred_wf(G, p_interp_op(o_add, P1, P2), b_int) :- pred_wf(G, P1, b_int), pred_wf(G, P2, b_int). pred_wf(G, p_interp_op(o_eq, P1, P2), b_bool) :- pred_wf(G, P1, T), pred_wf(G, P2, T). (* We can define a Horn clause with two heads, meaning both conclusions hold *) pred_wf(G, p_conj(P1, P2), b_bool), pred_wf(G, p_disj(P1, P2), b_bool) :- pred_wf(G, P1, b_bool), pred_wf(G, P2, b_bool). pred_wf(G, p_neg(P), b_bool) :- pred_wf(G, P, b_bool). pred_wf(G, p_ite(P1, P2, P3), T) :- pred_wf(G, P1, b_bool), pred_wf(G, P2, T), pred_wf(G, P3, T). ``` That leaves us just handling variables; to do so, we need to define what it means to look up a variable in an environment. Formulog's first-order functional programming comes in handy for defining this type of helper function: ``` fun lookup(x: var, g: env): typ option = match g with | [] => none | (y, t) :: rest => if x = y then some(t) else lookup(x, rest) end ``` The `option` type (with its constructors `none` and `some`) is built into Formulog. We can now define the judgment for variables, as well as our final judgment for type well-formedness. ``` pred_wf(G, p_var(V), B) :- lookup(V, G) = some(t_refined(B, _, _)). wf(G, t_refined(B, V, P), k_base) :- K = (V, t_refined(B, V, p_true)), pred_wf(K :: G, P, b_bool), ``` Note that in the rule defining a case of `pred_wf` we invoke the `lookup` function we defined previously. Formulog allows ML-style functions to be invoked from within Horn clauses. ### Converting Constraints and Predicates to SMT The next judgments we encounter in JV are those for entailment and subtyping (Figure 3.4). ![Figure 3.4](./images/figure_3_4.png) The rule Ent-Emp requires us to determine if a constraint is valid; we can do this in Formulog by using the built-in function `is_valid`, provided that we convert a term of type `constraint` to a term of type `bool smt` (the type in Formulog representing an SMT proposition). That doesn't sound too bad; we can write a function to do that. The conjunction case is straightforward: ``` fun constraint2smt(c: constraint): bool smt = match c with | c_conj(c1, c2) => let s1 = constraint2smt(c1) in let s2 = constraint2smt(c2) in `s1 /\ s2` (* TODO: other cases *) end ``` Note that SMT formulas are demarcated by backticks, and `/\` is the built-in notation for SMT conjunction. Now let's consider another case in the match statement, corresponding to the constructor `c_pred(pred)`. Here, we need to construct a term of type `bool smt` out of a term of type `pred`. Let's try to do that in a helper function, `pred2smt`: ``` fun pred2smt(p: pred): bool smt = match p with (* Putting a term in quotes makes it type at the SMT level *) | p_bool(b) => `b` | p_conj(p1, p2) => let b1 = pred2smt(p1) in let b2 = pred2smt(p2) in `b1 /\ b2` | p_disj(p1, p2) => let b1 = pred2smt(p1) in let b2 = pred2smt(p2) in `b1 \/ b2` | p_neg(p1) => let b = pred2smt(p1) in `~b` | p_interp_op(o_eq, p1, p2) => let b1 = pred2smt(p1) in let b2 = pred2smt(p2) in `b1 #= b2` (* TODO: other cases *) end ``` So far, so good. But how about when we get to the `p_int` case? The function signature requires us to return a term of type `bool smt`, but that doesn't make any sense in this case. In fact, if we take a closer look at how predicates are defined, we can see that the syntax for predicates allow bool-valued and int-valued terms to be mixed. We could go back, and try to redefine the syntax for predicates in a way that distinguishes between bool-valued and int-valued terms. However, even if this were possible, doing so would have a few downsides: - it takes us farther away from the formalism of JV; - it would likely lead to duplication, since we would need, e.g., two different constructors for equality, one where the subterms are ints and one where the subterms are bools; and - it does not seem like a very flexible approach as our language of predicates becomes more complex. There is another alternative, which is to push the bool-vs-int distinction into the SMT level, using the SMT theory of algebraic data types (this follows the encoding approach of the Dminor refinement type system [2]). To do so, we'll define a new algebraic data type, representing a value in a predicate (which will be either an integer or a bool): ``` type val = | v_int(int) | v_bool(bool) ``` This type will only appear in SMT formulas. We can then redefine `pred2smt` to return a term of type `val smt`---i.e., a `val`-valued SMT term---instead of a term of type `bool smt`: ``` fun pred2smt(p: pred): val smt = match p with | p_bool(b) => `v_bool(b)` | p_conj(p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in `v_bool(#v_bool_1(v1) /\ #v_bool_1(v2))` | p_disj(p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in `v_bool(#v_bool_1(v1) \/ #v_bool_1(v2))` | p_neg(p1) => let v1 = pred2smt(p1) in `v_bool(~#v_bool_1(v1))` | p_int(n) => `v_int(int_const(n))` | p_interp_op(o, p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in match o with | o_eq => `v_bool(v1 #= v2)` | o_add => `v_int(int_add(#v_int_1(v1), #v_int_1(v2)))` end | p_ite(p1, p2, p3) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in let v3 = pred2smt(p3) in `#if #v_bool_1(v1) then v2 else v3` | p_var(x) => `#{x}[val]` end ``` There's a lot going on here. Let's look at some simple cases first. - `` p_bool(b) => `v_bool(b)` ``: We have a term `b` of type `bool`; to turn it into a term of type `val`, we wrap it in the constructor `v_bool`; to turn this into a term of type `val smt`, we quote it with backticks. - `` p_int(n) => `v_int(int_const(n))` ``: We have a term `n` of type `i32`; we can turn it into a term of type `int smt` (a mathematical integer in SMT land) by wrapping it with the constructor `int_const`, and then supply this term as an argument to the `v_int` constructor to create a term of type `val smt`. Note that even though the `v_int` constructor is defined to take a term of type `int` and not `int smt`, the two types are conflated when occurring within an SMT formula (i.e., between backticks). - `` p_var(x) => `#{x}[val]` ``: we take a predicate-level variable `x` and construct an SMT-level variable named `x` that is typed as `val` within the SMT formula. Technically, the syntax `#{x}[val]` creates a Formulog term of type `val sym` (i.e., a `val`-valued SMT variable), and the backticks then raise the type to `val smt`. In the other cases, we see the use of the constructors `#v_int_1` and `#v_bool_1`. For all datatypes (that can be expressed at the SMT level), Formulog creates constructors of this form that can be used within SMT formulas to access the arguments of constructors. For example, `#v_int_1` is defined so that `#v_int_1(v_int(n))` is the int `n`, but `#v_int_1(v_bool(_))` is any int. This approach reflects the SMT-LIB theory of algebraic datatypes. Essentially, these constructors allow us to coerce (within an SMT formula) a value of type `val` to a `bool` or `int`. Now that we have `pred2smt`, we can go back and finish our definition of `constraint2smt`: ``` fun constraint2smt(c: constraint): bool smt = match c with | c_conj(c1, c2) => let s1 = constraint2smt(c1) in let s2 = constraint2smt(c2) in `s1 /\ s2` | c_pred(p) => let s = pred2smt(p) in `#v_bool_1(s)` | c_imp(x, _b, p1, c1) => (* Note that we do not actually need to use the basic type `_b` *) let prem = pred2smt(p1) in let conl = constraint2smt(c1) in (* This uses more special syntax for SMT formulas *) `forall #{x}[val]. #v_bool_1(prem) ==> conl` end ``` ### Entailment and Subtyping Now that we have a way to turn constraints into terms of type `bool smt`, we can start implementing the rules for entailment and subtyping (Figure 3.4, repeated from above). ![Figure 3.4](./images/figure_3_4.png) First, the rules for entailment: ``` rel ent(env, constraint) ent([], C) :- is_valid(constraint2smt(C)). ent((X, t_refined(B, X, P)) :: G, C) :- ent(G, c_imp(X, B, P, C)). ``` The function `is_valid` is a built-in that calls out to an external SMT solver. For the subtyping rules, we need to define helper functions for substituting variables in predicates (described briefly in Section 2.2) and types (explicitly given in Section 3.3.1). ![Section 3.3.1](./images/section_3_3_1.png) ``` fun subst_pred(p: pred, y: var, z: var): pred = match p with | p_bool(_) | p_int(_) => p | p_var(x) => p_var(if x = y then z else x) | p_interp_op(o, p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_interp_op(o, p1, p2) | p_disj(p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_disj(p1, p2) | p_conj(p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_conj(p1, p2) | p_neg(p1) => let p1 = subst_pred(p1, y, z) in p_neg(p1) | p_ite(p1, p2, p3) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in let p3 = subst_pred(p3, y, z) in p_ite(p1, p2, p3) end fun subst_typ(t: typ, y: var, z: var): typ = match t with | t_refined(b, v, p) => if v = y then t else t_refined(b, v, subst_pred(p, y, z)) | t_func(x, s, t) => let s = subst_typ(s, y, z) in let t = if x = y then t else subst_typ(t, y, z) in t_func(x, s, t) end ``` These functions are pretty standard. We can then define the rules for subtyping: ``` rel sub(env, typ, typ) sub(G, t_refined(B, V1, P1), t_refined(B, V2, P2)) :- ent(G, c_imp(V1, B, P1, c_pred(subst_pred(P2, V2, V1)))). sub(G, t_func(X1, S1, T1), t_func(X2, S2, T2)) :- sub(G, S2, S1), sub((X2, S2) :: G, subst_typ(T1, X1, X2), T2). ``` ### Type Synthesis Given the machinery we have in place, the rules for type synthesis (Figure 3.5) fall into place nicely. ![Figure 3.5](./images/figure_3_5.png) ``` rel syn(env, expr, typ) (* Declare relation for type checking, so we can refer to it *) rel chk(env, expr, typ) syn(G, e_var(X), T) :- lookup(X, G) = some(T). (* Here we give an int n the type int{v : v = n} *) syn(_G, e_int(N), t_refined(b_int, "v", P)) :- P = p_interp_op(o_eq, p_var("v"), p_int(N)). syn(G, e_annot(E, T), T) :- wf(G, T, _k), chk(G, E, T). syn(G, e_app(E, Y), subst_typ(T, X, Y)) :- syn(G, E, t_func(X, S, T)), chk(G, e_var(Y), S). ``` ### Type Checking The type checking rules themselves (Figure 3.6) are also straightforward. ![Figure 3.6](./images/figure_3_6.png) ``` chk(G, E, T) :- syn(G, E, S), sub(G, S, T). chk(G, e_lambda(X, E), t_func(X, T1, T2)) :- chk((X, T1) :: G, E, T2). chk(G, e_let(X, E1, E2), T2) :- syn(G, E1, T1), chk((X, T1) :: G, E2, T2). ``` ## Running Some Examples Now that we have all the logic in place, let's try to type check some terms! First, we'll add a little machinery for running examples: ``` (* We'll populate this relation with numbered examples *) rel ex(i32, env, expr, typ) rel check_ex(i32) check_ex(N) :- ex(N, G, E, T), chk(G, E, T). (* This is a query that checks all examples; replace the _ with a number to check a particular example *) :- check_ex(_). ``` ### Checking Literals Let's try a simple example first: ``` (* The type int{v: v = n} *) fun lit_typ(n: i32): typ = t_refined(b_int, "v", p_interp_op(o_eq, p_var("v"), p_int(n))) (* Expr 42 Type int{v: v = 42} *) ex(0, [], e_int(42), lit_typ(42)). ``` We can ask Formulog to dump the derived queries: ``` java -jar formulog.jar tutorial.flg --dump-query ``` Doing so, we'll see that `check_ex(0)` is derived: ``` ==================== QUERY RESULTS (1) ==================== query:check_ex(0) ``` So, our type checker works on that example! But maybe it accepts every term? Let's try an example that should fail: ``` (* Expr 42 Type int{v: v = 43} *) ex(1, [], e_int(42), lit_typ(43)). ``` When we run Formulog now, we see that `check_ex(1)` is *not* derived; and so our type checker has successfully rejected that example! ### Checking Let Bindings Here's an example involving a simple let binding: ``` (* Expr let z = 42 in z Type int{v: v = 42} *) ex(2, [], E, T) :- E = e_let("z", e_int(42), e_var("z")), T = lit_typ(42). ``` When we run this, we see that the fact `check_ex(2)` is not derived, which is wrong: this example should go through. What's up? We can debug this example by adding `print` statements to rules to essentially track the progress of the type checker. For example, if the first of these print functions fires, but the second one does not, then we know that the entailment call failed: ``` sub(G, t_refined(B, V1, P1), t_refined(B, V2, P2)) :- print(("sub ent 0", G, V1, P1, V2, P2)), ent(G, c_imp(V1, B, P1, c_pred(subst_pred(P2, V2, V1)))), print(("sub ent 1", G, V1, P1, V2, P2)). ``` Print is a function that takes any type (here a tuple) and returns `true` (a function can be used in a rule body as an atom if it returns a bool). Admittedly, this is not a great debugging experience; hopefully we can come up with something better. Nonetheless, with a little effort, we see that type checking fails on the recursive entailment rule (which faithfully implements the rule Ent-Ext in JV): ``` ent((X, t_refined(B, X, P)) :: G, C) :- ent(G, c_imp(X, B, P, C)). ``` The issue is that the judgment is too strict: it requires that the name of the variable in the context is the same as the name of the bound variable in the refinement. We can fix this by adding another rule if the names are different; this rule changes the name of the variable in the refinement to match the name of the variable in the context, as long as that name does not already appear in the refinement: ``` fun appears(x: var, p: pred): bool = match p with | p_var(y) => x = y | p_bool(_) | p_int(_) => false | p_interp_op(_, p1, p2) | p_conj(p1, p2) | p_disj(p1, p2) => appears(x, p1) || appears(x, p2) | p_neg(p1) => appears(x, p1) | p_ite(p1, p2, p3) => appears(x, p1) || appears(x, p2) || appears(x, p3) end (* An additional rule *) ent((X, t_refined(B, Y, P)) :: G, C) :- X != Y && !appears(X, P), ent(G, c_imp(X, B, subst_pred(P, Y, X), C)). ``` Now the type checker works on this example! (This isn't the most general solution: a better technique would be to create a fresh variable and substitute it for `Y` in `P` and `X` in `C`; however, this is good enough for now.) ### Checking Functions Let's see if we can type a constant function: ``` (* Expr \y. 0 Type y:int{y: true} -> int{v: v = 0} *) ex(5, [], e_lambda("y", e_int(0)), t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0))). ``` It works! How about if we bind that function, but do not actually use it? (Note that lambdas need to be annotated with a type using the `e_annot` constructor, as there are no rules for synthesizing a type for a lambda.) ``` (* Expr let z = \y. 0 in let x = 42 in x Type int{v: v = 42} *) ex(6, [], E, T) :- Lam_type = t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0)), E1 = e_let("x", e_int(42), e_var("x")), E = e_let("z", e_annot(e_lambda("y", e_int(0)), Lam_type), E1), T = lit_typ(42). ``` This fails! What gives? If we do some more debugging, we see that entailment is once again an issue: there is no rule for what to do if there is a variable in the context with a function type (in this case, the program variable "z"). We can fix that by adding another entailment rule that skips over the binding, as long as the bound variable does not appear free in the constraint: ``` fun is_free(x: var, c: constraint): bool = match c with | c_pred(p) => appears(x, p) | c_conj(c1, c2) => is_free(x, c1) || is_free(x, c2) | c_imp(y, _, p, c1) => x != y && (appears(x, p) || is_free(x, c1)) end (* An additional rule *) ent((X, t_func(_, _, _)) :: G, C) :- !is_free(X, C), ent(G, C). ``` Now the example works :) How about if we apply the function? ``` (* Expr let z = \y. 0 in let x = 42 in z x Type int{v: v = 0} *) ex(7, [], E, T) :- Lam_type = t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0)), E1 = e_let("x", e_int(42), e_app(e_var("z"), "x")), E = e_let("z", e_annot(e_lambda("y", e_int(0)), Lam_type), E1), T = lit_typ(0). ``` It works! ### Checking Addition So far, our examples have not included very interesting refinements. Let's try to type check an expression involving addition. Since our language of expressions does not actually include operations like addition, we will include an "add" function in the context with the appropriate type. ``` (* The type of the add function *) const add_typ: typ = let add = p_interp_op(o_add, p_var("x"), p_var("y")) in let eq = p_interp_op(o_eq, p_var("v"), add) in let r = t_refined(b_int, "v", eq) in let t = t_func("y", t_refined(b_int, "v", p_bool(true)), r) in let s = t_refined(b_int, "v", p_bool(true)) in t_func("x", s, t) (* Context add |-> x:int{v: true} -> (y:int{v: true} -> int{v: v = x + y}) one |-> int{v: v = 1} Expr let z = 41 in add z one Type int{v: v = 42} *) ex(8, G, E, T) :- G = [("add", add_typ), ("one", lit_typ(1))], E = e_let("z", e_int(41), e_app(e_app(e_var("add"), "z"), "one")), T = lit_typ(42). ``` It works! We've been able to prove, in the type system, that our expression evaluates to 42. ## Exercises Adventurous readers might want to take on these extra exercises. ### Add an Uninterpreted Operation Our language of predicates includes interpreted operations (like addition and equality), but no uninterpreted operations (the grammar of JV includes both). Add a new `pred` constructor `p_gcd(pred, pred)` that represents the greatest common denominator of two terms. When a `p_gcd` predicate is converted to an SMT term in the function `pred2smt`, emit an uninterpreted function term. SMT-level uninterpreted functions can be declared in Formulog using syntax like: ``` uninterpreted fun foo(i32 smt, string smt): bool smt ``` You'll also have to update rules/functions as appropriate, to handle the new constructor case. ### Add Branches and Recursion Implement the additional rules for branches and recursion in JV Sections 4.2 and 4.3. We have not tried this ourselves; let us know (by raising a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new)) if you run into difficulties! From skimming the new rules, the trickiest part appears to be creating a fresh variable `y` in the Chk-If rule. One approach you might consider is changing the type of variables from `string` to `val sym` (i.e., a `val`-valued SMT variable): ``` type var = val sym ``` This will require a bunch of updates in the existing code (a bit of a pain), but it has the advantage that it is now very easy to create a fresh variable. For example, say that you have a context `g` and an expr `e`: the variable `#{(g, e)}[val]` is guaranteed to not occur in either `g` or `e`---i.e., it's fresh. (Here, we are using the tuple `(g, e)` as the "name" of the variable.) We have found this trick to be useful in implementing more complex type systems. ### Check Out More Complex Formulog Examples For our [Formulog publications]({{ site.base_url }}{% link pubs.md %}), we have built three substantial, relatively sophisticated SMT-based case studies. After going through this tutorial, you might find it interesting to check out the code for these case studies. While the analyses are more complex than the tutorial example (and, admittedly, not as well documented as they could be), this tutorial will have hopefully armed you with the information to understand a lot of what's happening in them. - [Dminor](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/dminor/bench.flg) [2]: a refinement type checker that allows dynamic type tests, so that types can refer to expressions, and expressions can refer to types. Type checking also involves proving that expressions are pure, which requires termination checking. - [Scuba](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/scuba/bench.flg) [3]: a context-sensitive, bottom-up points-to analysis for Java that uses SMT formulas to summarize the effects of methods on the points-to graph. - [Symex](https://github.com/aaronbembenek/making-formulog-fast/blob/main/benchmarks/symex/bench.flg): a KLEE-style [4] symbolic executor for a fragment of LLVM bitcode corresponding to simple C programs with loops and arrays. ## Conclusions In this tutorial, we've seen how to mechanize the formal specification of an interesting program analysis---the declarative rules for bidirectional refinement type checking---by encoding that specification directly in Formulog. It's neat to be able to program so close to the formal specification; as we've seen, doing so has even allowed us to identify a few possible gaps in the inference rules (i.e., the missing rules for entailment). Furthermore, now that you have a Formulog implementation of the analysis, you can rely on Formulog's language infrastructure to apply both high-level and low-level optimizations to the analysis. For example, Formulog's parallel evaluation techniques can speed up type checking in the presence of multiple code units. Additionally, the [compiler]({{ site.base_url }}{% link eval_modes/compile.md %}) from Formulog to Soufflé makes it possible to automatically derive a decently efficient C++ version of the type checker. We hope you have enjoyed this dive into Formulog! As we mentioned earlier, please raise a [GitHub issue](https://github.com/HarvardPL/formulog/issues/new) for questions, comments, and feedback :) ## References [1] Ranjit Jhala and Niki Vazou. 2020. Refinement Types: A Tutorial. arXiv:2010.07763v1. [2] Gavin M. Bierman, Andrew D. Gordon, Cătălin Hriţcu, and David Langworthy. 2012. Semantic Subtyping with an SMT Solver. Journal of Functional Programming 22, 1 (2012), 31–105. [3] Yu Feng, Xinyu Wang, Isil Dillig, and Thomas Dillig. 2015. Bottom-up Context-Sensitive Pointer Analysis for Java. In Proceedings of the 13th Asian Symposium on Programming Languages and Systems. 465–484. [4] Cristian Cadar, Daniel Dunbar, and Dawson Engler. 2008. KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs. In Proceedings of the 8th USENIX Conference on Operating Systems Design and Implementation. 209–224. ================================================ FILE: examples/greeting.flg ================================================ (*- * #%L * Formulog * %% * Copyright (C) 2018 - 2019 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% *) @edb rel entity(string) entity("Alice"). entity("Bob"). entity("World"). rel greeting(string) greeting(Y) :- entity(X), some(M) = get_model([`#y[string] #= str_concat("Hello, ", X)`], none), some(Y) = query_model(#y[string], M). ================================================ FILE: examples/liquid_types.flg ================================================ (*- * #%L * Formulog * %% * Copyright (C) 2018 - 2019 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% *) (******************************************************************************* LIQUID_TYPES.FLG This file has the implementation of a type checker for a simple language with refinement types. *******************************************************************************) type tvar = string type var = string sym type label = string type op = | op_not | op_or | op_and | op_eqb type base = | base_bool type typ = | typ_tvar(tvar) | typ_fun(var, typ, typ) | typ_forall(tvar, typ) | typ_ref(var, base, exp) and exp = | exp_var(var) | exp_bool(bool) | exp_op(op) | exp_lam(var, typ, exp) | exp_tlam(tvar, exp) | exp_app(exp, exp) | exp_tapp(exp, typ) fun append(L1 : 'a list, L2 : 'a list) : 'a list = match L1 with | [] => L2 | X :: L1rest => X :: append(L1rest, L2) end fun remove(X : 'a, L : 'a list) : 'a list = match L with | [] => [] | Y :: Lrest => if X = Y then remove(X, Lrest) else Y :: remove(X, Lrest) end fun elem(X : 'a, L : 'a list) : bool = match L with | [] => false | Y :: Lrest => X = Y || elem(X, Lrest) end const x : var = #x[string] const y : var = #y[string] const b : var = #b[string] const b1 : var = #b1[string] const b2 : var = #b2[string] const v : var = #v[string] fun fresh_for(NAME : var, VARS : var list) : var = if elem(NAME, VARS) then (* okay, we have work to do *) #{VARS}[string] else (* we're fine---the name is already fresh *) NAME fun typ_freevars(T : typ) : var list = match T with | typ_tvar(_) => [] | typ_fun(X, T1, T2) => append(typ_freevars(T1), remove(X, typ_freevars(T2))) | typ_forall(_, Tinner) => typ_freevars(Tinner) | typ_ref(X, _B, Eref) => remove(X, exp_freevars(Eref)) end and exp_freevars(E : exp) : var list = match E with | exp_var(X) => [X] | exp_bool(_) => [] | exp_lam(X, T, Elam) => append(typ_freevars(T), remove(X, exp_freevars(Elam))) | exp_tlam(_, Etlam) => exp_freevars(Etlam) | exp_app(E1, E2) => append(exp_freevars(E1), exp_freevars(E2)) | exp_tapp(Etapp, Ttapp) => append(exp_freevars(Etapp), typ_freevars(Ttapp)) end (* SUGAR *) fun typ_base(B : base) : typ = typ_ref(v, B, exp_bool(true)). fun typ_simple(Tdom : typ, Tcod : typ) : typ = typ_fun(v, Tdom, Tcod) fun exp_not(E : exp) : exp = exp_app(exp_op(op_not), E). fun exp_or(E1 : exp, E2 : exp) : exp = exp_app(exp_app(exp_op(op_or), E1), E2). fun exp_and(E1 : exp, E2 : exp) : exp = exp_app(exp_app(exp_op(op_and), E1), E2). fun exp_eqb(T : typ, E1 : exp, E2 : exp) : exp = exp_app(exp_app(exp_tapp(exp_op(op_eqb), T), E1), E2) fun typ_isref(T : typ) : bool = match T with | typ_ref(_, _, _) => true | _ => false end fun typ_op(O : op) : typ = match O with | op_not => typ_fun(b, typ_base(base_bool), typ_base(base_bool)) | op_or => typ_fun(b1, typ_base(base_bool), typ_fun(b2, typ_base(base_bool), typ_base(base_bool))) | op_and => typ_fun(b1, typ_base(base_bool), typ_fun(b2, typ_base(base_bool), typ_base(base_bool))) | op_eqb => typ_forall("A", typ_fun(x, typ_base(base_bool), typ_fun(y, typ_base(base_bool), typ_ref(v,base_bool, exp_eqb(typ_tvar("A"), exp_var(v), exp_app(exp_app(exp_op(op_eqb), exp_var(x)), exp_var(y))))))) end fun typ_subst(X : var, E : exp, T : typ) : typ = match T with | typ_tvar(_) => T | typ_fun(Y, T1, T2) => let Yfresh = fresh_for(Y, X::append(exp_freevars(E), typ_freevars(T2))) in let T2fresh = if Y = Yfresh then T2 else typ_subst(Y, exp_var(Yfresh), T2) in typ_fun(Yfresh, typ_subst(X, E, T1), typ_subst(X, E, T2fresh)) | typ_forall(A, Tinner) => typ_forall(A, typ_subst(X, E, Tinner)) | typ_ref(Y, B, Eref) => let Yfresh = fresh_for(Y, X::append(exp_freevars(E), exp_freevars(Eref))) in let Ereffresh = if Y = Yfresh then Eref else exp_subst(Y, exp_var(Yfresh), Eref) in typ_ref(Yfresh, B, exp_subst(X, E, Ereffresh)) end and exp_subst(X: var, E : exp, Etgt : exp) : exp = match Etgt with | exp_var(Y) => if X = Y then E else Etgt | exp_bool(_) => Etgt | exp_op(_) => Etgt | exp_lam(Y, Tlam, Elam) => let Yfresh = fresh_for(Y, X::append(typ_freevars(Tlam), exp_freevars(Elam))) in let Elamfresh = if Y = Yfresh then Elam else exp_subst(Y, exp_var(Yfresh), Elam) in exp_lam(Yfresh, typ_subst(X, E, Tlam), Elamfresh) | exp_tlam(A, Etlam) => exp_tlam(A, exp_subst(X, E, Etlam)) | exp_app(E1, E2) => exp_app(exp_subst(X, E, E1), exp_subst(X, E, E2)) | exp_tapp(Etapp, T) => exp_tapp(exp_subst(X, E, Etapp), typ_subst(X, E, T)) end fun typ_tsubst(A : tvar, T : typ, Ttgt : typ) : typ = match T with | typ_tvar(B) => if A = B then T else Ttgt | typ_fun(X, T1, T2) => typ_fun(X, typ_tsubst(A, T, T1), typ_tsubst(A, T, T2)) | typ_forall(B, Tinner) => let Tnew = if A = B then Tinner else typ_tsubst(A, T, Tinner) in typ_forall(B, Tnew) | typ_ref(X, B, Eref) => typ_ref(X, B, exp_tsubst(A, T, Eref)) end and exp_tsubst(A : tvar, T : typ, E : exp) : exp = match E with | exp_var(_) | exp_bool(_) => E | exp_op(_) => E | exp_lam(X, Tlam, Elam) => exp_lam(X, typ_tsubst(A, T, Tlam), exp_tsubst(A, T, Elam)) | exp_tlam(B, Etlam) => let Enew = if A = B then Etlam else exp_tsubst(A, T, Etlam) in exp_tlam(B, Enew) | exp_app(E1, E2) => exp_app(exp_tsubst(A, T, E1), exp_tsubst(A, T, E2)) | exp_tapp(Etapp, Ttapp) => exp_tapp(exp_tsubst(A, T, Etapp), typ_tsubst(A, T, Ttapp)) end type ctx = | ctx_empty | ctx_var(ctx, var, typ) | ctx_tvar(ctx, tvar) | ctx_exp(ctx, exp) fun lookup(X : var, G : ctx) : typ option = match G with | ctx_empty => none | ctx_var(Grest, Y, T) => if X = Y then some(T) else lookup(X, Grest) | ctx_tvar(Grest, _) => lookup(X, Grest) | ctx_exp(Grest, _) => lookup(X, Grest) end fun bound_tvar(A : tvar, G : ctx) : bool = match G with | ctx_empty => false | ctx_var(Grest, _, _) => bound_tvar(A, Grest) | ctx_tvar(Grest, B) => A = B || bound_tvar(A, Grest) | ctx_exp(Grest, _) => bound_tvar(A, Grest) end @topdown rel encode_ctx(ctx, bool smt) @topdown rel encode_exp(exp, bool smt) @topdown rel sub(ctx, typ, typ) @topdown rel wf_ctx(ctx) @topdown rel wf_typ(ctx, typ) @topdown rel wf_exp(ctx, exp, typ) @topdown rel synth(ctx, exp, typ) @topdown rel check(ctx, exp, typ) (*******************************************************************************) (* SMT ENCODING ****************************************************************) encode_ctx(ctx_empty, `true`). encode_ctx(ctx_tvar(Grest, _), Phi) :- encode_ctx(Grest, Phi). encode_ctx(ctx_exp(Grest, E), `Phirest /\ PhiE`) :- encode_ctx(Grest, Phirest), encode_exp(E, PhiE). encode_ctx(ctx_var(Grest, X, typ_ref(Y, _B, E)), `Phirest /\ PhiE`) :- encode_ctx(Grest, Phirest), Eprime = exp_subst(Y, exp_var(X), E), encode_exp(Eprime, PhiE). encode_ctx(ctx_var(Grest, _, T), Phirest) :- encode_ctx(Grest, Phirest), !typ_isref(T). encode_exp(exp_var(X), V) :- V = `#{X}[bool]`. (* We would need a more sophisticated encoding if there were other types *) encode_exp(exp_bool(true), `true`). encode_exp(exp_bool(false), `false`). encode_exp(exp_app(exp_op(op_not), E), `~Phi`) :- encode_exp(E, Phi). encode_exp(exp_app(exp_app(exp_op(op_and), E1), E2), `Phi1 /\ Phi2`) :- encode_exp(E1, Phi1), encode_exp(E2, Phi2). encode_exp(exp_app(exp_app(exp_op(op_or), E1), E2), `Phi1 \/ Phi2`) :- encode_exp(E1, Phi1), encode_exp(E2, Phi2). encode_exp(exp_app(exp_app(exp_tapp(exp_op(op_eqb), _T), E1), E2), `Phi1 #= Phi2`) :- encode_exp(E1, Phi1), encode_exp(E2, Phi2). (*******************************************************************************) (* SUBTYPING *******************************************************************) sub(G, typ_ref(X, B, E1), typ_ref(Y, B, E2)) :- wf_ctx(G), exp_subst(Y, exp_var(X), E2) = E2prime, encode_ctx(G, PhiG), encode_exp(E1, Phi1), encode_exp(E2prime, Phi2), is_valid(`PhiG /\ Phi1 ==> Phi2`). sub(G, typ_tvar(A), typ_tvar(A)) :- wf_ctx(G). sub(G, typ_fun(X, T11, T12), typ_fun(Y, T21, T22)) :- sub(G, T21, T11), typ_subst(Y, exp_var(X), T22) = T22prime, sub(ctx_var(G, X, T21), T12, T22prime). sub(G, typ_forall(A, T1), typ_forall(B, T2)) :- typ_tsubst(B, typ_tvar(A), T2) = T2prime, sub(ctx_tvar(G, A), T1, T2prime). (*******************************************************************************) (* WELL FORMEDNESS OF CONTEXTS *************************************************) wf_ctx(ctx_empty). wf_ctx(ctx_var(G, _X, T)) :- wf_ctx(G), wf_typ(G, T). wf_ctx(ctx_tvar(G, _)) :- wf_ctx(G). (*******************************************************************************) (* WELL FORMEDNESS OF TYPES ****************************************************) wf_typ(G, typ_tvar(A)) :- wf_ctx(G), bound_tvar(A, G). wf_typ(G, typ_fun(X, T1, T2)) :- wf_typ(G, T1), wf_typ(ctx_var(G, X, T1), T2). wf_typ(G, typ_forall(A, T)) :- wf_typ(ctx_tvar(G, A), T). wf_typ(G, typ_ref(_X, _B, exp_bool(true))) :- wf_ctx(G). wf_typ(G, typ_ref(X, B, E)) :- E != exp_bool(true), wf_typ(G, typ_base(B)), check(ctx_var(G,X,typ_base(B)), E, typ_base(base_bool)). (*******************************************************************************) (* WELL FORMEDNESS OF TERMS (i.e., TYPING) *************************************) check(G, E, T) :- synth(G, E, Tprime), sub(G, Tprime, T). synth(G, exp_var(X), T) :- wf_ctx(G), lookup(X, G) = some(T), !typ_isref(T). synth(G, exp_var(X), typ_ref(Vfresh, B, exp_eqb(T, exp_var(Vfresh), exp_var(V)))) :- wf_ctx(G), lookup(X, G) = some(T), T = typ_ref(V, B, _E), fresh_for(v, typ_freevars(T)) = Vfresh. synth(G, exp_bool(true), typ_ref(v, base_bool, exp_var(v))) :- wf_ctx(G). synth(G, exp_bool(false), typ_ref(v, base_bool, exp_app(exp_op(op_not), exp_var(v)))) :- wf_ctx(G). synth(G, exp_op(O), typ_op(O)) :- wf_ctx(G). synth(G, exp_lam(X, T1, E), T) :- wf_typ(G, T1), synth(ctx_var(G, X, T1), E, T2), typ_fun(X, T1, T2) = T. synth(G, exp_app(E1, E2), T) :- synth(G, E1, typ_fun(X, T1, T2)), check(G, E2, T1), typ_subst(X, E2, T2) = T. synth(G, exp_tapp(E, Targ), T) :- synth(G, E, typ_forall(A, Tinner)), typ_tsubst(A, Targ, Tinner) = T. (* Try uncommenting these different queries (one query allowed at a time); you might want to run Formulog with the `--dump-query` option. *) (* :- check(ctx_empty, exp_bool(true), typ_ref(v, base_bool, exp_var(v))). *) (* :- check(ctx_empty, exp_bool(true), typ_base(base_bool)). *) (* :- wf_typ(ctx_empty, typ_base(base_bool)). *) (* :- check(ctx_var(ctx_empty, x, typ_base(base_bool)), exp_var(x), typ_base(base_bool)). *) (* :- wf_typ(ctx_empty, typ_ref(x, base_bool, exp_var(x))). *) :- wf_typ(ctx_empty, typ_ref(x, base_bool, exp_app(exp_op(op_not), exp_var(x)))). ================================================ FILE: examples/symeval.flg ================================================ (*- * #%L * Formulog * %% * Copyright (C) 2018 - 2019 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% *) (******************************************************************************* SYMEVAL.FLG This file has the implementation of a bounded symbolic evaluator for a simple imperative language. *******************************************************************************) type ('k, 'v) map = ('k * 'v) list fun get(K:'k, Map:('k, 'v) map) : 'v option = match Map with | [] => none | (K2, V) :: Rest => if K = K2 then some(V) else get(K, Rest) end fun put(K:'k, V:'v, Map:('k, 'v) map) : ('k, 'v) map = (K, V) :: Map type node = i32 type var = string type cond = | cond_eq | cond_ne | cond_lt | cond_le | cond_gt | cond_ge type binop = | binop_add | binop_mul | binop_div | binop_rem type unop = | unop_neg type inst = | inst_jmp(cond, var, var, node) | inst_goto(node) | inst_binop(var, binop, var, var) | inst_unop(var, unop, var) | inst_fail | inst_assign(var, var) type val = | v_conc(i32) | v_symb(bv[32] sym) type store = (var, val) map type state = (store * bool smt) @edb rel fall_thru_succ(node, node) @edb rel start(node, store) @edb rel stmt(node, inst) rel reach(node, state, i32) rel failed_assert(node, state) rel stepping_to(node, state, i32) @edb rel max_steps(i32) fun get_store(State:state) : store = match State with (Store, _) => Store end fun get_constraints(State:state) : bool smt = match State with (_, Constraints) => Constraints end fun update_store(Reg:var, Val:val, State:state) : state = let (Store, Constraints) = State in let New_store = put(Reg, Val, Store) in (New_store, Constraints) fun update_constraints(Constraint:bool smt, State:state) : state = let (Store, Constraints) = State in (Store, `Constraint /\ Constraints`) fun flip_cond(Cond:cond) : cond = match Cond with | cond_eq => cond_ne | cond_ne => cond_eq | cond_lt => cond_ge | cond_le => cond_gt | cond_gt => cond_le | cond_ge => cond_lt end fun handle_unop(Dest:var, Op:unop, Val:var, State:state) : state = let Store = get_store(State) in match get(Val, Store) with | some(v_conc(X)) => let Res = match Op with | unop_neg => -X end in update_store(Dest, v_conc(Res), State) | some(v_symb(Val)) => let X = #{State}[bv[32]] in let Neg = match Op with | unop_neg => `bv_neg(Val)` end in let State = update_constraints(`X #= Neg`, State) in update_store(Dest, v_symb(X), State) end fun coerce_symbolic_val(Val:val) : bv[32] smt = match Val with v_conc(X) | v_symb(X) => `X` end fun handle_binop(Dest:var, Op:binop, Val1:var, Val2:var, State:state) : state = let Store = get_store(State) in let some(Val1) = get(Val1, Store) in let some(Val2) = get(Val2, Store) in match (Val1, Val2) with | (v_conc(X1), v_conc(X2)) => let Res = match Op with | binop_add => X1 + X2 | binop_mul => X1 * X2 | binop_div => X1 / X2 | binop_rem => X1 % X2 end in update_store(Dest, v_conc(Res), State) | _ => let X = #{State}[bv[32]] in let Val1 = coerce_symbolic_val(Val1) in let Val2 = coerce_symbolic_val(Val2) in let Res = match Op with | binop_add => `bv_add(Val1, Val2)` | binop_mul => `bv_mul(Val1, Val2)` | binop_div => `bv_sdiv(Val1, Val2)` | binop_rem => `bv_srem(Val1, Val2)` end in let State = update_constraints(`X #= Res`, State) in update_store(Dest, v_symb(X), State) end fun handle_cond(Cond:cond, Reg1:var, Reg2:var, State:state) : state option = let (Store, Constraints) = State in let some(Val1) = get(Reg1, Store) in let some(Val2) = get(Reg2, Store) in match (Val1, Val2) with | (v_conc(X1), v_conc(X2)) => let Feasible = match Cond with | cond_eq => X1 = X2 | cond_ne => X1 != X2 | cond_lt => X1 < X2 | cond_le => X1 <= X2 | cond_gt => X1 > X2 | cond_ge => X1 >= X2 end in if Feasible then some(State) else none | _ => let Val1 = coerce_symbolic_val(Val1) in let Val2 = coerce_symbolic_val(Val2) in let Constraint = match Cond with | cond_eq => `Val1 #= Val2` | cond_ne => `~(Val1 #= Val2)` | cond_lt => `bv_slt(Val1, Val2)` | cond_le => `~bv_sgt(Val1, Val2)` | cond_gt => `bv_sgt(Val1, Val2)` | cond_ge => `~bv_slt(Val1, Val2)` end in let New_constraints = `Constraint /\ Constraints` in if is_sat(New_constraints) then let New_state = (Store, New_constraints) in some(New_state) else none end stepping_to(Node, (Store, `true`), 0) :- start(Node, Store). stepping_to(Succ, New_state, D) :- fall_thru_succ(Node, Succ), stmt(Node, inst_assign(Dest, Val)), reach(Node, State, D), some(X) = get(Val, get_store(State)), New_state = update_store(Dest, X, State). stepping_to(Succ, New_state, D) :- fall_thru_succ(Node, Succ), stmt(Node, inst_unop(Dest, Op, Val)), reach(Node, State, D), New_state = handle_unop(Dest, Op, Val, State). stepping_to(Succ, New_state, D) :- fall_thru_succ(Node, Succ), stmt(Node, inst_binop(Dest, Op, Val1, Val2)), reach(Node, State, D), New_state = handle_binop(Dest, Op, Val1, Val2, State). stepping_to(Succ, New_state, D) :- stmt(Node, inst_jmp(Cond, Val1, Val2, Succ)), reach(Node, State, D), some(New_state) = handle_cond(Cond, Val1, Val2, State). stepping_to(Succ, New_state, D) :- fall_thru_succ(Node, Succ), stmt(Node, inst_jmp(Cond, Val1, Val2, _)), reach(Node, State, D), some(New_state) = handle_cond(flip_cond(Cond), Val1, Val2, State). stepping_to(Succ, State, D) :- stmt(Node, inst_goto(Succ)), reach(Node, State, D). max_steps(10). reach(Node, State, D + 1) :- stepping_to(Node, State, D), max_steps(X), D < X. failed_assert(Node, State) :- stmt(Node, inst_fail), reach(Node, State, _). (* our query... comment this out to exhaustively explore the program states *) :- failed_assert(_N, _S). fun vc(N:i32) : val = v_conc(N). fun vs(X:string) : val = v_symb(#{X}[bv[32]]). (* example 01 *) (* start(0, []). *) (* example 02 x = 21 + 21; *) (* start(0, [("21", vc(21))]). stmt(0, inst_binop("x", binop_add, "21", "21")). fall_thru_succ(0, 1). *) (* example 03 x = mksym(); if (x != x) { fail(); } *) (* start(0, [("x", vs("x"))]). stmt(0, inst_jmp(cond_eq, "x", "x", 2)). stmt(1, inst_fail). fall_thru_succ(0, 1). *) (* example 04 x = mksym(); if (x == 0) { x = x + 1; } if (x == 0) { fail(); } *) (* start(0, [("0", vc(0)), ("1", vc(1)), ("x", vs("x"))]). stmt(0, inst_jmp(cond_ne, "x", "0", 2)). stmt(1, inst_binop("x", binop_add, "x", "1")). stmt(2, inst_jmp(cond_ne, "x", "0", 4)). stmt(3, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 2). fall_thru_succ(2, 3). *) (* example 05 x = mksym(); y = mksym(); if (x < 0) { x = -x; } if (y < 0) { y = -y; } z = x + y; if (z < 0) { fail(); } *) (* start(2, [("0", vc(0)), ("x", vs("x")), ("y", vs("y"))]). stmt(2, inst_jmp(cond_ge, "x", "0", 4)). stmt(3, inst_unop("x", unop_neg, "x")). stmt(4, inst_jmp(cond_ge, "y", "0", 6)). stmt(5, inst_unop("y", unop_neg, "y")). stmt(6, inst_binop("z", binop_add, "x", "y")). stmt(7, inst_jmp(cond_ge, "z", "0", 9)). stmt(8, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). fall_thru_succ(4, 5). fall_thru_succ(5, 6). fall_thru_succ(6, 7). fall_thru_succ(7, 8). *) (* example 06 euclid's gCD algorithm x0 = 0; x1 = ...; x2 = ...; while (x2 != x0) { x3 = x2; x2 = x1 % x2; x1 = x3; } *) (* start(0, [("x0", vc(0)), ("x2", vc(1071)), ("x1", vc(462))]). stmt(0, inst_jmp(cond_eq, "x2", "x0", 1)). stmt(2, inst_assign("x3", "x2")). stmt(3, inst_binop("x2", binop_rem, "x1", "x2")). stmt(4, inst_assign("x1", "x3")). stmt(5, inst_goto(0)). fall_thru_succ(0, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). fall_thru_succ(4, 5). *) (* example 07 x0 = 42; if (x0 == x0) { fail(); } else { x0 = x0; x0 = x0; x0 = x0; } *) (* stmt(0, inst_jmp(cond_ne, "x", "x", 2)). stmt(1, inst_fail). stmt(2, inst_assign("x", "x")). stmt(3, inst_assign("x", "x")). stmt(4, inst_assign("x", "x")). fall_thru_succ(0, 1). fall_thru_succ(2, 3). fall_thru_succ(3, 4). start(0, [("x", vc(42))]). *) (* example 08 x = mksym(); while (x == x) { // do nothing } fail(); *) (* stmt(1, inst_jmp(cond_ne, "x", "x", 2)). fall_thru_succ(0, 1). fall_thru_succ(1, 1). stmt(2, inst_fail). start(1, [("x", vs("x"))]). *) (* example 09 goto label2; label1: goto label1; label2: fail(); *) (* stmt(0, inst_goto(2)). stmt(1, inst_goto(1)). stmt(2, inst_fail). start(0, []). *) (* example 10 x = mksym(); y = mksym(); goto label; while (x == y) { // do nothing } label: fail(); *) (* stmt(0, inst_goto(2)). stmt(1, inst_jmp(cond_ne, "x", "y", 2)). stmt(2, inst_fail). fall_thru_succ(1, 1). start(0, [("x", vs("x")), ("y", vs("y"))]). *) (* example 11 x = mksym(); y = mksym(); while (x == y) { // do nothing } fail(); *) (* stmt(0, inst_jmp(cond_ne, "x", "y", 1)). stmt(1, inst_fail). fall_thru_succ(0, 0). start(0, [("x", vs("x")), ("y", vs("y"))]). *) (* example 12 goto end; while (true) {}; fail(); *) (* stmt(0, inst_goto(2)). stmt(1, inst_jmp(cond_ne, "1", "1", 2)). stmt(2, inst_fail). fall_thru_succ(1, 1). start(0, [("1", vc(1))]). *) (* example 13 x0 = mksymb(); if (x0 == x0) { fail(); } else { x0 = x0; x0 = x0; x0 = x0; } *) (* stmt(0, inst_jmp(cond_ne, "x", "x", 2)). stmt(1, inst_fail). stmt(2, inst_assign("x", "x")). stmt(3, inst_assign("x", "x")). stmt(4, inst_assign("x", "x")). fall_thru_succ(0, 1). fall_thru_succ(2, 3). fall_thru_succ(3, 4). start(0, [("x", vs("x"))]). *) (* example 14 x0 = mksymb(); if (x0 != x0) { x0 = x0; x0 = x0; x0 = x0; } else { fail(); } *) (* stmt(0, inst_jmp(cond_eq, "x", "x", 1)). stmt(1, inst_fail). stmt(2, inst_assign("x", "x")). stmt(3, inst_assign("x", "x")). stmt(4, inst_assign("x", "x")). fall_thru_succ(0, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). start(0, [("x", vs("x"))]). *) (* example 15 x = mksym(); y = mksym(); while (x != y) { x = x + x; } fail(); *) (* stmt(0, inst_jmp(cond_eq, "x", "y", 2)). stmt(1, inst_binop("x", binop_add, "x", "x")). stmt(2, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 0). start(0, [("x", vs("x")), ("y", vs("y"))]). *) (* example 15 x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; x = x + x; fail(); *) (* stmt(0, inst_binop("x", binop_add, "x", "x")). stmt(1, inst_binop("x", binop_add, "x", "x")). stmt(2, inst_binop("x", binop_add, "x", "x")). stmt(3, inst_binop("x", binop_add, "x", "x")). stmt(4, inst_binop("x", binop_add, "x", "x")). stmt(5, inst_binop("x", binop_add, "x", "x")). stmt(6, inst_binop("x", binop_add, "x", "x")). stmt(7, inst_binop("x", binop_add, "x", "x")). stmt(8, inst_binop("x", binop_add, "x", "x")). stmt(9, inst_binop("x", binop_add, "x", "x")). stmt(10, inst_binop("x", binop_add, "x", "x")). stmt(11, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). fall_thru_succ(4, 5). fall_thru_succ(5, 6). fall_thru_succ(6, 7). fall_thru_succ(7, 8). fall_thru_succ(8, 9). fall_thru_succ(9, 10). fall_thru_succ(10, 11). start(0, [("x", vc(1))]). *) (* example 16 if (x == y) { x = x + x; x = x + x; } else { y = y + y; } fail(); *) (* stmt(0, inst_jmp(cond_ne, "x", "y", 3)). stmt(1, inst_binop("x", binop_add, "x", "x")). stmt(2, inst_binop("x", binop_add, "x", "x")). stmt(3, inst_binop("y", binop_add, "y", "y")). stmt(4, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 2). fall_thru_succ(2, 4). fall_thru_succ(3, 4). start(0, [("x", vs("x")), ("y", vs("y"))]). *) (* example 17 x0 = mksymb(); y = 42; if (x0 != y) { x0 = x0; x0 = x0; x0 = x0; } else { fail(); } *) (* stmt(0, inst_jmp(cond_eq, "x", "y", 1)). stmt(1, inst_fail). stmt(2, inst_assign("x", "x")). stmt(3, inst_assign("x", "x")). stmt(4, inst_assign("x", "x")). fall_thru_succ(0, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). start(0, [("x", vs("x")), ("y", vc(42))]). *) (* example 18 x = mksymb(); y = mksymb(); z = mksymb(); 0: if (x != 0) 1: x = x; 2: if (y != 0) 3: y = y; 4: if (z != 0) 5: z = z; 6: fail(); *) stmt(0, inst_jmp(cond_eq, "x", "0", 2)). stmt(1, inst_assign("x", "x")). stmt(2, inst_jmp(cond_eq, "y", "0", 4)). stmt(3, inst_assign("y", "y")). stmt(4, inst_jmp(cond_eq, "z", "0", 6)). stmt(5, inst_assign("z", "z")). stmt(6, inst_fail). fall_thru_succ(0, 1). fall_thru_succ(1, 2). fall_thru_succ(2, 3). fall_thru_succ(3, 4). fall_thru_succ(4, 5). fall_thru_succ(5, 6). start(0, [("x", vs("x")), ("y", vs("y")), ("z", vs("z")), ("0", vc(0))]). ================================================ FILE: examples/tutorial.flg ================================================ (*** This is the full code listing for the Formulog tutorial, which is available at . ***) (******************************************************************************* DEFINITIONS *******************************************************************************) (* An algebraic data type with two variants *) type basic_typ = | b_int | b_bool (* A type alias *) type var = string (* Interpreted operations: for simplicity, we'll support just a few *) type op = | o_add | o_eq type pred = | p_var(var) | p_bool(bool) (* i32 is the type of a 32-bit signed integer *) | p_int(i32) | p_interp_op(op, pred, pred) | p_conj(pred, pred) | p_disj(pred, pred) | p_neg(pred) | p_ite(pred, pred, pred) type constraint = | c_pred(pred) | c_conj(constraint, constraint) | c_imp(var, basic_typ, pred, constraint) type typ = | t_refined(basic_typ, var, pred) | t_func(var, typ, typ) type kind = | k_base | k_star (* Tuples and lists are built-in types *) type env = (var * typ) list type expr = | e_int(i32) | e_var(var) | e_let(var, expr, expr) | e_lambda(var, expr) | e_app(expr, var) | e_annot(expr, typ) (******************************************************************************* WELL-FORMEDNESS *******************************************************************************) rel wf(env, typ, kind) wf(G, T, k_star) :- wf(G, T, k_base). wf(G, t_func(X, S, T), k_star) :- wf(G, S, _Ks), wf((X, S) :: G, T, _Kt). rel pred_wf(env, pred, basic_typ) pred_wf(_G, p_bool(_B), b_bool). pred_wf(_G, p_int(_I), b_int). pred_wf(G, p_interp_op(o_add, P1, P2), b_int) :- pred_wf(G, P1, b_int), pred_wf(G, P2, b_int). pred_wf(G, p_interp_op(o_eq, P1, P2), b_bool) :- pred_wf(G, P1, T), pred_wf(G, P2, T). (* We can define a Horn clause with two heads, meaning both conclusions hold *) pred_wf(G, p_conj(P1, P2), b_bool), pred_wf(G, p_disj(P1, P2), b_bool) :- pred_wf(G, P1, b_bool), pred_wf(G, P2, b_bool). pred_wf(G, p_neg(P), b_bool) :- pred_wf(G, P, b_bool). pred_wf(G, p_ite(P1, P2, P3), T) :- pred_wf(G, P1, b_bool), pred_wf(G, P2, T), pred_wf(G, P3, T). fun lookup(x: var, g: env): typ option = match g with | [] => none | (y, t) :: rest => if x = y then some(t) else lookup(x, rest) end pred_wf(G, p_var(V), B) :- lookup(V, G) = some(t_refined(B, _, _)). wf(G, t_refined(B, V, P), k_base) :- K = (V, t_refined(B, V, p_bool(true))), pred_wf(K :: G, P, b_bool). (******************************************************************************* CONVERTING CONSTRAINTS AND PREDICATES TO SMT *******************************************************************************) type val = | v_int(int) | v_bool(bool) fun pred2smt(p: pred): val smt = match p with | p_bool(b) => `v_bool(b)` | p_conj(p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in `v_bool(#v_bool_1(v1) /\ #v_bool_1(v2))` | p_disj(p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in `v_bool(#v_bool_1(v1) \/ #v_bool_1(v2))` | p_neg(p1) => let v1 = pred2smt(p1) in `v_bool(~#v_bool_1(v1))` | p_int(n) => `v_int(int_const(n))` | p_interp_op(o, p1, p2) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in match o with | o_eq => `v_bool(v1 #= v2)` | o_add => `v_int(int_add(#v_int_1(v1), #v_int_1(v2)))` end | p_ite(p1, p2, p3) => let v1 = pred2smt(p1) in let v2 = pred2smt(p2) in let v3 = pred2smt(p3) in `#if #v_bool_1(v1) then v2 else v3` | p_var(x) => `#{x}[val]` end fun constraint2smt(c: constraint): bool smt = match c with | c_conj(c1, c2) => let s1 = constraint2smt(c1) in let s2 = constraint2smt(c2) in `s1 /\ s2` | c_pred(p) => let s = pred2smt(p) in `#v_bool_1(s)` | c_imp(x, _b, p1, c1) => (* Note that we do not actually need to use the basic type `_b` *) let prem = pred2smt(p1) in let conl = constraint2smt(c1) in (* This uses more special syntax for SMT formulas *) `forall #{x}[val]. #v_bool_1(prem) ==> conl` end (******************************************************************************* ENTAILMENT AND SUBTYPING *******************************************************************************) fun subst_pred(p: pred, y: var, z: var): pred = match p with | p_bool(_) | p_int(_) => p | p_var(x) => p_var(if x = y then z else x) | p_interp_op(o, p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_interp_op(o, p1, p2) | p_disj(p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_disj(p1, p2) | p_conj(p1, p2) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in p_conj(p1, p2) | p_neg(p1) => let p1 = subst_pred(p1, y, z) in p_neg(p1) | p_ite(p1, p2, p3) => let p1 = subst_pred(p1, y, z) in let p2 = subst_pred(p2, y, z) in let p3 = subst_pred(p3, y, z) in p_ite(p1, p2, p3) end rel ent(env, constraint) ent([], C) :- is_valid(constraint2smt(C)). fun appears(x: var, p: pred): bool = match p with | p_var(y) => x = y | p_bool(_) | p_int(_) => false | p_interp_op(_, p1, p2) | p_conj(p1, p2) | p_disj(p1, p2) => appears(x, p1) || appears(x, p2) | p_neg(p1) => appears(x, p1) | p_ite(p1, p2, p3) => appears(x, p1) || appears(x, p2) || appears(x, p3) end ent((X, t_refined(B, Y, P)) :: G, C) :- X != Y && !appears(X, P), ent(G, c_imp(X, B, subst_pred(P, Y, X), C)). ent((X, t_refined(B, Y, P)) :: G, C) :- X = Y || !appears(X, P), ent(G, c_imp(X, B, subst_pred(P, Y, X), C)). ent((X, t_refined(B, X, P)) :: G, C) :- ent(G, c_imp(X, B, P, C)). fun is_free(x: var, c: constraint): bool = match c with | c_pred(p) => appears(x, p) | c_conj(c1, c2) => is_free(x, c1) || is_free(x, c2) | c_imp(y, _, p, c1) => x != y && (appears(x, p) || is_free(x, c1)) end ent((X, t_func(_, _, _)) :: G, C) :- !is_free(X, C), ent(G, C). fun subst_typ(t: typ, y: var, z: var): typ = match t with | t_refined(b, v, p) => if v = y then t else t_refined(b, v, subst_pred(p, y, z)) | t_func(x, s, t) => let s = subst_typ(s, y, z) in let t = if x = y then t else subst_typ(t, y, z) in t_func(x, s, t) end rel sub(env, typ, typ) sub(G, t_refined(B, V1, P1), t_refined(B, V2, P2)) :- ent(G, c_imp(V1, B, P1, c_pred(subst_pred(P2, V2, V1)))). sub(G, t_func(X1, S1, T1), t_func(X2, S2, T2)) :- sub(G, S2, S1), sub((X2, S2) :: G, subst_typ(T1, X1, X2), T2). (******************************************************************************* TYPE SYNTHESIS *******************************************************************************) rel syn(env, expr, typ) (* Declare relation for type checking, so we can refer to it *) rel chk(env, expr, typ) syn(G, e_var(X), T) :- lookup(X, G) = some(T). (* Here we give an int n the type int{v : v = n} *) syn(_G, e_int(N), t_refined(b_int, "v", P)) :- P = p_interp_op(o_eq, p_var("v"), p_int(N)). syn(G, e_annot(E, T), T) :- wf(G, T, _k), chk(G, E, T). syn(G, e_app(E, Y), subst_typ(T, X, Y)) :- syn(G, E, t_func(X, S, T)), chk(G, e_var(Y), S). (******************************************************************************* TYPE CHECKING *******************************************************************************) chk(G, E, T) :- syn(G, E, S), sub(G, S, T). chk(G, e_lambda(X, E), t_func(X, T1, T2)) :- chk((X, T1) :: G, E, T2). chk(G, e_let(X, E1, E2), T2) :- syn(G, E1, T1), chk((X, T1) :: G, E2, T2). (******************************************************************************* RUNNING SOME EXAMPLES *******************************************************************************) rel ex(i32, env, expr, typ) rel check_ex(i32) check_ex(N) :- ex(N, G, E, T), chk(G, E, T). :- check_ex(_). fun lit_typ(n: i32): typ = t_refined(b_int, "v", p_interp_op(o_eq, p_var("v"), p_int(n))) (* Expr 42 Type int{v: v = 42} *) ex(0, [], e_int(42), lit_typ(42)). (* Expr 43 Type int{v: v = 43} *) ex(1, [], e_int(42), lit_typ(43)). (* Expr let z = 42 in z Type int{v: v = 42} *) ex(2, [], E, T) :- E = e_let("z", e_int(42), e_var("z")), T = lit_typ(42). (* Expr let y = 0 in let x = 42 in x Type int{v: v = 42} *) ex(3, [], E, T) :- E1 = e_let("x", e_int(42), e_var("x")), E = e_let("y", e_int(0), E1), T = lit_typ(42). (* Expr let x = 0 in let x = 42 in x Type int{v: v = 42} *) ex(4, [], E, T) :- E1 = e_let("x", e_int(42), e_var("x")), E = e_let("x", e_int(0), E1), T = lit_typ(42). (* Expr \y. 0 Type y:int{y: true} -> int{v: v = 0} *) ex(5, [], e_lambda("y", e_int(0)), t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0))). (* Expr let z = \y. 0 in let x = 42 in x Type int{v: v = 42} *) ex(6, [], E, T) :- Lam_type = t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0)), E1 = e_let("x", e_int(42), e_var("x")), E = e_let("z", e_annot(e_lambda("y", e_int(0)), Lam_type), E1), T = lit_typ(42). (* Expr let z = \y. 0 in let x = 42 in z x Type int{v: v = 0} *) ex(7, [], E, T) :- Lam_type = t_func("y", t_refined(b_int, "y", p_bool(true)), lit_typ(0)), E1 = e_let("x", e_int(42), e_app(e_var("z"), "x")), E = e_let("z", e_annot(e_lambda("y", e_int(0)), Lam_type), E1), T = lit_typ(0). (* The type of the add function *) const add_typ: typ = let add = p_interp_op(o_add, p_var("x"), p_var("y")) in let eq = p_interp_op(o_eq, p_var("v"), add) in let r = t_refined(b_int, "v", eq) in let t = t_func("y", t_refined(b_int, "v", p_bool(true)), r) in let s = t_refined(b_int, "v", p_bool(true)) in t_func("x", s, t) (* Context add |-> x:int{v: true} -> (y:int{v: true} -> int{v: v = x + y}) one |-> int{v: v = 1} Expr let z = 41 in add z one Type int{v: v = 42} *) ex(8, G, E, T) :- G = [("add", add_typ), ("one", lit_typ(1))], E = e_let("z", e_int(41), e_app(e_app(e_var("add"), "z"), "one")), T = lit_typ(42). ================================================ FILE: license-header ================================================ /*- * #%L * Formulog * %% * Copyright (C) $YEAR President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ ================================================ FILE: misc/flg.vim ================================================ " Vim syntax file " Language: Formulog " Maintainer: Aaron Bembenek " Latest Revision: 9 May 2019 if exists("b:current_syntax") finish endif syn match number "\v<[0-9]+(\.[0-9]*)*[lLfFdD]?>" syn match number "\v<[0-9]*\.[0-9]+[lLfFdD]?>" syn match keywords ":-" syn match keywords "\~" syn match keywords "#=" syn match keywords '::' syn match keywords "|" syn match keywords "&" syn match keywords "+" syn match keywords "-" syn match keywords "*" syn match keywords "/" syn match keywords "\\" syn match keywords "!" syn match keywords "=" syn match keywords "<" syn match keywords ">" syn match keywords "\." syn match keywords "{" syn match keywords "}" syn match keywords "<\[" syn match keywords "]>" syn keyword keywords type rel const match let if then else end fun in with uninterpreted and sort syn keyword todo contained TODO XXX FIXME syn region comment start="(\*" end="\*)" fold contains=todo,comment syn keyword typeKeywords i32 i64 fp32 fp64 list bool option cmp string smt bv fp sym int array syn match variable "\v<[A-Z][a-zA-Z0-9_]*>" syn match keywords "#let" syn match keywords "#if" syn match annotation "@\v<[a-zA-Z0-9_]+>" syn match bool "true" syn match bool "false" syn region string start=/\v"/ skip=/\v\\./ end=/\v"/ syn region formula start="`" end="`" contains=number,string,keywords,comment,variable,typeKeywords let b:current_syntax = "flg" hi def link number Constant hi def link string Constant hi def link bool Constant hi def link keywords Statement hi def link comment Comment hi def link typeKeywords Type hi def link variable Identifier hi def link todo Todo hi def link annotation Special hi def link formula Special ================================================ FILE: pom.xml ================================================ 4.0.0 edu.harvard.seas.pl formulog 0.8.0-SNAPSHOT formulog UTF-8 junit junit test 4.13.2 org.jgrapht jgrapht-core 1.5.3 org.antlr antlr4 4.13.2 org.pcollections pcollections 5.0.0 org.apache.commons commons-lang3 3.20.0 org.apache.bcel bcel 6.12.0 com.fasterxml.jackson.core jackson-databind 2.21.3 info.picocli picocli 4.7.7 org.antlr antlr4-maven-plugin 4.13.2 true antlr antlr4 maven-assembly-plugin edu.harvard.seas.pl.formulog.Main jar-with-dependencies make-assembly package single org.codehaus.mojo license-maven-plugin 2.7.1 org.apache.maven.plugins maven-compiler-plugin 3.15.0 11 11 com.diffplug.spotless spotless-maven-plugin 3.4.0 1.23.0 ${project.basedir}/license-header check ================================================ FILE: src/main/antlr4/edu/harvard/seas/pl/formulog/parsing/generated/Formulog.g4 ================================================ grammar Formulog; prog : ( metadata | stmt )* EOF ; tsvFile : tabSeparatedTermLine* EOF ; tabSeparatedTermLine : (term (TAB term)*)? NEWLINE ; // Program metadata //////////////////////////////////////////////////////////// metadata : topLevelFunDefs '.'? # funDecl | annotation* relType = 'rel' ID maybeAnnotatedTypeList '.'? # relDecl | 'type' typeDefLHS EQ type '.'? # typeAlias | 'type' typeDefLHS EQ typeDefRHS ( 'and' typeDefLHS EQ typeDefRHS )* '.'? # typeDecl | 'uninterpreted' 'fun' constructorType ':' type '.'? # uninterpFunDecl | 'uninterpreted' 'sort' typeDefLHS '.'? # uninterpSortDecl ; maybeAnnotatedTypeList : '(' (var ':')? type (',' (var ':')? type)* ')' | // can be empty ; funDefLHS : ID args = varTypeList ':' retType = type ; topLevelFunDefs : intro = (FUN | CONST) funDefLHS EQ term ( 'and' funDefLHS EQ term )* ; funDefs : FUN funDefLHS EQ term ( 'and' funDefLHS EQ term )* ; constructorType : ID typeList ; var : VAR | ID ; varTypeList : '(' var ':' type ( ',' var ':' type )* ')' | // can be empty ; typeList : '(' type ( ',' type )* ')' | // can be empty ; type0 : '(' type ')' # parenType | TYPEVAR # typeVar | type0 ID parameterList # typeRef | ( '(' type ( ',' type )* ')' )? ID parameterList # typeRef ; type : type0 ( '*' type0 )* # tupleType ; parameterList : '[' parameter ( ',' parameter )* ']' | // can be empty ; parameter : '?' # wildCardParam | type # typeParam | INT # intParam ; typeDefLHS : ( TYPEVAR | '(' TYPEVAR ( ',' TYPEVAR )* ')' )? ID ; typeDefRHS : adtDef | recordDef ; adtDef : '|'? constructorType ( '|' constructorType )* | // can be empty ; recordDef : '{' recordEntryDef ( ';' recordEntryDef )* ';'? '}' ; recordEntryDef : ID ':' type ; annotation : '@' ID ; // Program logic /////////////////////////////////////////////////////////////// stmt : clause # clauseStmt | fact # factStmt | query # queryStmt ; clause : head = nonEmptyTermList ':-' body = nonEmptyTermList '.' ; fact : term '.' ; query : ':-' term '.' ; predicate : ID termArgs ; functor : id = ( ID | XID | XVAR ) parameterList termArgs # indexedFunctor ; termArgs : ( '(' ( term ( ',' term )* ) ')' )? ; term : HOLE # holeTerm | 'fold' '[' ID ']' termArgs # foldTerm | functor # functorTerm | list # listTerm | tuple # tupleTerm | '(' term ')' # parensTerm | op = ( MINUS | BANG | PLUS ) term # unopTerm | term op = ( MUL | DIV | REM ) term # binopTerm | term op = ( PLUS | MINUS ) term # binopTerm | < assoc = right > term '::' term # consTerm | term op = ( LT | LTE | GT | GTE ) term # binopTerm | term op = ( EQ | NEQ ) term # binopTerm | term op = AMP term # binopTerm | term op = CARET term # binopTerm | term op = AMPAMP term # binopTerm | term op = BARBAR term # binopTerm | VAR # varTerm | QSTRING # stringTerm | val = ( INT | HEX ) # i32Term | val = (INTL | HEXL) # i64Term | val = FP64 # doubleTerm | val = FP32 # floatTerm | val = ( FP32_NAN | FP32_POS_INFINITY | FP32_NEG_INFINITY | FP64_NAN | FP64_POS_INFINITY | FP64_NEG_INFINITY ) # specialFPTerm | '{' recordEntries '}' # recordTerm | '{' term 'with' recordEntries '}' # recordUpdateTerm | '`' term '`' # formulaTerm | '#' '{' term '}' '[' parameter ']' # termSymFormula | NOT term # notFormula | < assoc = left > term op = FORMULA_EQ term # binopFormula | < assoc = right > term op = AND term # binopFormula | < assoc = right > term op = OR term # binopFormula | < assoc = right > term op = IMP term # binopFormula | < assoc = right > term op = IFF term # binopFormula | '#let' term EQ term 'in' term # letFormula | quantifier = ( FORALL | EXISTS ) variables = nonEmptyTermList ( ':' pattern = nonEmptyTermList )? '.' boundTerm = term # quantifiedFormula | '#if' term 'then' term 'else' term # iteTerm | term ISNOT ID # outermostCtor | 'match' term 'with' '|'? matchClause ( '|' matchClause )* 'end' # matchExpr | 'let' lhs = letBind '=' assign = term 'in' body = term # letExpr | 'let' funDefs 'in' letFunBody = term # letFunExpr | 'if' guard = term 'then' thenExpr = term 'else' elseExpr = term # ifExpr ; recordEntries : recordEntry ( ';' recordEntry )* ';'? ; recordEntry : ID '=' term ; letBind : ( term | '(' term ',' term ( ',' term )* ')' ) ; nonEmptyTermList : term ( ',' term )* ; list : '[' ( term ( ',' term )* )? ']' ; tuple : '(' term ',' term ( ',' term )* ')' ; matchClause : pats = patterns '=>' rhs = term ; patterns : term ( '|' term )* ; // Tokens ////////////////////////////////////////////////////////////////////// AND : '/\\' ; OR : '\\/' ; IMP : '==>' ; IFF : '<==>' ; NOT : '~' ; FORMULA_EQ : '#=' ; FP32_NAN : 'fp32_nan' ; FP32_POS_INFINITY : 'fp32_pos_infinity' ; FP32_NEG_INFINITY : 'fp32_neg_infinity' ; FP64_NAN : 'fp64_nan' ; FP64_POS_INFINITY : 'fp64_pos_infinity' ; FP64_NEG_INFINITY : 'fp64_neg_infinity' ; TYPEVAR : '\'' ID ; XVAR : '#' VAR ; VAR : [A-Z_] [a-zA-Z0-9_]* ; INT : [0-9]+ ; INTL : [0-9]+ ('l' | 'L') ; HEX : '0x' [0-9a-fA-F]+ ; HEXL : '0x' [0-9a-fA-F]+ ('l' | 'L') ; fragment FP : INT '.' INT ; fragment FPE : ( FP | INT ) ( 'e' | 'E' ) ('+'|'-')? INT ; FP32 : ( FP | INT | FPE ) ( 'F' | 'f' ) ; FP64 : ( FP | FPE ) ( 'D' | 'd' )? | INT ( 'D' | 'd' ) ; LT : '<' ; LTE : '<=' ; GT : '>' ; GTE : '>=' ; MUL : '*' ; DIV : '/' ; REM : '%' ; PLUS : '+' ; MINUS : '-' ; BANG : '!' ; CARET : '^' ; AMP : '&' ; BARBAR : '||' ; AMPAMP : '&&' ; ISNOT : 'not' ; EQ : '=' ; NEQ : '!=' ; FORALL : 'forall' ; EXISTS : 'exists' ; FUN : 'fun' ; CONST : 'const' ; HOLE : '??' ; NEWLINE : ('\r\n' | [\n\r]) -> channel(HIDDEN) ; TAB : '\t' -> channel(HIDDEN) ; SPACES : [ ]+ -> skip ; COMMENT : '(*' ( COMMENT | . )*? '*)' -> skip ; XID : '#' ID ; ID : [a-z] [a-zA-Z0-9_]* ; fragment ESCAPE : '\\' . ; QSTRING : '"' ( ESCAPE | ~( '\n' | '\r' | '"' | '\\' ) )* '"' ; ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/Configuration.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.db.IndexedFactDb; import edu.harvard.seas.pl.formulog.eval.IndexedRule; import edu.harvard.seas.pl.formulog.smt.CheckSatAssumingSolver; import edu.harvard.seas.pl.formulog.smt.PushPopSolver; import edu.harvard.seas.pl.formulog.smt.SmtLibSolver; import edu.harvard.seas.pl.formulog.smt.SmtStatus; import edu.harvard.seas.pl.formulog.smt.SmtStrategy; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.util.Dataset; import edu.harvard.seas.pl.formulog.util.EnumerableThreadLocal; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.SharedLong; import edu.harvard.seas.pl.formulog.util.Util; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public final class Configuration { private Configuration() { throw new AssertionError("impossible"); } public static final boolean runTests = propIsSet("runTests"); public static final String testFile = System.getProperty("testFile"); public static final boolean recordFuncDiagnostics = propIsSet("timeFuncs"); private static final Map funcTimes = new ConcurrentHashMap<>(); public static final boolean recordRuleDiagnostics = propIsSet("timeRules"); private static final Map, Pair> ruleTimes = new ConcurrentHashMap<>(); public static final boolean debugSmt = propIsSet("debugSmt"); public static final String debugSmtOutDir = getStringProp("debugSmtOutDir", "solver_logs"); public static final boolean timeSmt = propIsSet("timeSmt"); public static final boolean smtMemoize = propIsSet("smtMemoize", true); private static final Map perProcessSmtEvalStats = new ConcurrentHashMap<>(); private static final Dataset smtEvalStats = new Dataset(); private static final AtomicLong smtDeclGlobalsTime = new AtomicLong(); private static final AtomicLong smtEncodeTime = new AtomicLong(); private static final AtomicLong smtDeclTime = new AtomicLong(); private static final AtomicLong smtInferTime = new AtomicLong(); private static final AtomicLong smtSerialTime = new AtomicLong(); public static final SharedLong smtWaitTime = new SharedLong(); private static final AtomicInteger smtNumCallsSat = new AtomicInteger(); private static final AtomicInteger smtNumCallsUnsat = new AtomicInteger(); private static final AtomicInteger smtNumCallsUnknown = new AtomicInteger(); private static final AtomicInteger smtNumCallsDoubleCheck = new AtomicInteger(); private static final AtomicInteger smtNumCallsFalseUnknown = new AtomicInteger(); // XXX I don't think this is being used any more private static final AtomicLong smtTotalTime = new AtomicLong(); public static void recordSmtTime(long delta) { smtTotalTime.addAndGet(delta); } public static long getSmtTotalTime() { return smtTotalTime.get(); } public static final boolean printRelSizes = propIsSet("printRelSizes"); public static final boolean printFinalRules = propIsSet("printFinalRules"); public static final boolean simplifyFormulaVars = propIsSet("simplifyFormulaVars", true); public static final boolean debugRounds = propIsSet("debugRounds"); public static final boolean debugParallelism = propIsSet("debugParallelism"); public static final int optimizationSetting = getIntProp("optimize", 0); public static final int taskSize = getIntProp("taskSize", 128); public static final int smtTaskSize = getIntProp("smtTaskSize", 8); public static final int smtCacheSize = getIntProp("smtCacheSize", 100); public static final String smtSolver; static { smtSolver = getStringProp("smtSolver", "z3"); switch (smtSolver) { case "z3": case "cvc4": case "yices": case "boolector": break; default: throw new IllegalArgumentException("Unrecognized solver: " + smtSolver); } } public static final String smtLogic = getStringProp("smtLogic", "ALL"); public static final boolean smtDeclareAdts = propIsSet("smtDeclareAdts", true); public static final boolean smtCacheHardResets = propIsSet("smtCacheHardResets", false); public static final boolean smtUseNegativeLiterals = propIsSet("smtUseNegativeLiterals", false); public static final boolean smtDoubleCheckUnknowns = propIsSet("smtDoubleCheckUnknowns", true); public static final boolean smtUseSingleShotSolver = propIsSet("smtUseSingleShotSolver", false) || smtSolver.equals("boolector"); public static final boolean smtCheckSuccess = propIsSet("smtCheckSuccess", false); private static final Dataset pushPopStackSize = new Dataset(); private static final Dataset pushPopStackReuse = new Dataset(); private static final Dataset pushPopStackPushes = new Dataset(); private static final Dataset pushPopStackPops = new Dataset(); private static final Dataset pushPopStackDelta = new Dataset(); private static final Dataset csaCacheHitRate = new Dataset(); private static final Dataset csaCacheUseRate = new Dataset(); private static final Dataset csaCacheSize = new Dataset(); private static final Dataset csaCacheHits = new Dataset(); private static final Dataset csaCacheMisses = new Dataset(); private static final AtomicInteger csaCacheClears = new AtomicInteger(); private static final Dataset csaEvalStats = new Dataset(); private static final Dataset pushPopEvalStats = new Dataset(); private static final Dataset otherSolverEvalStats = new Dataset(); public static final boolean oneRuleAtATime = propIsSet("oneRuleAtATime"); public static final boolean parallelizeInnerLoops = propIsSet("parallelizeInnerLoops", true); public static final boolean useDemandTransformation = propIsSet("useDemandTransformation", true); public static final boolean restoreStratification = propIsSet("restoreStratification", true); public static final List trackedRelations = getListProp("trackedRelations"); public static final boolean debugMst = propIsSet("debugMst"); public static final boolean debugStratification = propIsSet("debugStratification"); public static final String debugStratificationOutDir = getStringProp("debugStratificationOutDir", "stratification_graphs"); public static final boolean testCodeGen = propIsSet("testCodeGen"); public static final boolean testCodeGenEager = propIsSet("testCodeGenEager"); public static final boolean keepCodeGenTestDirs = propIsSet("keepCodeGenTestDirs"); public static final String cxxCompiler = getStringProp("cxxCompiler", null); public static final String souffleInclude = System.getProperty("souffleInclude"); public static final String boostInclude = System.getProperty("boostInclude"); public static final String boostLib = System.getProperty("boostLib"); public static final String tbbInclude = System.getProperty("tbbInclude"); public static final String tbbLib = System.getProperty("tbbLib"); public static final String outputExec = System.getProperty("outputExec"); public static final int memoizeThreshold() { return getIntProp("memoizeThreshold", 0); } public static final boolean genComparators = propIsSet("genComparators", true); public static final boolean minIndex = propIsSet("minIndex", true); public static final boolean inlineInRules = propIsSet("inlineInRules", true); public static final boolean eagerSemiNaive = propIsSet("eagerSemiNaive"); public static final boolean codegenSplitOnSmt = propIsSet("codegenSplitOnSmt"); public static final boolean useHashDbFilter = propIsSet("useHashDbFilter"); public static final boolean recordWork = propIsSet("recordWork"); public static final boolean recordDetailedWork = propIsSet("recordDetailedWork"); public static final SharedLong work = new SharedLong(); public static final SharedLong workItems = new SharedLong(); public static final SharedLong newDerivs = new SharedLong(); public static final SharedLong dupDerivs = new SharedLong(); public static final Map workPerRule = new ConcurrentHashMap<>(); public static class SmtStats { public long time; public long ncalls; public void add(long time) { this.time += time; ncalls++; } } public static final EnumerableThreadLocal smtTime = new EnumerableThreadLocal<>(SmtStats::new); public static final SharedLong smtCacheClears = new SharedLong(); public static final SharedLong smtCacheHits = new SharedLong(); public static final SharedLong smtCacheMisses = new SharedLong(); static { if (recordFuncDiagnostics) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { printFuncDiagnostics(System.err); } }); } if (recordRuleDiagnostics) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { printRuleDiagnostics(System.err); } }); } if (timeSmt) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { printSmtDiagnostics(System.err); } }); } Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { printConfiguration(System.err); } }); } public static synchronized void printConfiguration(PrintStream out) { // out.println("[CONFIG] noResults=" + noResults); // out.println("[CONFIG] timeRules=" + recordRuleDiagnostics); // out.println("[CONFIG] timeFuncs=" + recordFuncDiagnostics); // out.println("[CONFIG] timeSmt=" + timeSmt); // out.println("[CONFIG] optimize=" + optimizationSetting); // out.println("[CONFIG] taskSize=" + taskSize); // out.println("[CONFIG] smtTaskSize=" + smtTaskSize); // out.println("[CONFIG] memoizeThreshold=" + memoizeThreshold()); // out.println("[CONFIG] noModel=" + noModel()); } public static void recordSmtDeclTime(long time) { smtDeclTime.addAndGet(time); } public static void recordSmtInferTime(long time) { smtInferTime.addAndGet(time); } public static void recordSmtSerialTime(long time) { smtSerialTime.addAndGet(time); } public static void recordSmtEvalTime( SmtLibSolver solver, long encodeTime, long evalTime, SmtStatus result) { smtEncodeTime.addAndGet(encodeTime); smtEvalStats.addDataPoint(evalTime); if (solver instanceof CheckSatAssumingSolver) { csaEvalStats.addDataPoint(evalTime); } else if (solver instanceof PushPopSolver) { pushPopEvalStats.addDataPoint(evalTime); } else { otherSolverEvalStats.addDataPoint(evalTime); } Util.lookupOrCreate(perProcessSmtEvalStats, solver, () -> new Dataset()).addDataPoint(evalTime); switch (result) { case SATISFIABLE: smtNumCallsSat.incrementAndGet(); break; case UNKNOWN: smtNumCallsUnknown.incrementAndGet(); break; case UNSATISFIABLE: smtNumCallsUnsat.incrementAndGet(); break; } } public static void recordSmtWaitTime(long time) { smtWaitTime.add(time); } public static void recordSmtDeclGlobalsTime(long time) { smtDeclGlobalsTime.addAndGet(time); } public static synchronized void printSmtDiagnostics(PrintStream out) { Dataset callsPerSolver = new Dataset(); Dataset timePerSolver = new Dataset(); for (Dataset ds : perProcessSmtEvalStats.values()) { callsPerSolver.addDataPoint(ds.size()); timePerSolver.addDataPoint(ds.computeSum()); } out.println("[SMT WAIT TIME] " + smtWaitTime.unsafeGet() / 1e6 + "ms"); out.println("[SMT DECL GLOBALS TIME] " + smtDeclGlobalsTime.get() / 1e6 + "ms"); out.println("[SMT ENCODE TIME - TOTAL] " + smtEncodeTime.get() / 1e6 + "ms"); out.println("[SMT ENCODE TIME - DECL] " + smtDeclTime.get() / 1e6 + "ms"); out.println("[SMT ENCODE TIME - INFER] " + smtInferTime.get() / 1e6 + "ms"); out.println("[SMT ENCODE TIME - SERIAL] " + smtSerialTime.get() / 1e6 + "ms"); out.printf("[SMT EVAL TIME] %1.1fms%n", smtEvalStats.computeSum() / 1e6); out.println("[SMT EVAL TIME PER CALL (ms)] " + smtEvalStats.getStatsString(1e-6)); out.println("[SMT EVAL TIME PER SOLVER (ms)] " + timePerSolver.getStatsString(1e-6)); out.println("[SMT NUM CALLS PER SOLVER] " + callsPerSolver.getStatsString()); out.println("[SMT NUM CALLS - SAT] " + smtNumCallsSat); out.println("[SMT NUM CALLS - UNSAT] " + smtNumCallsUnsat); out.println("[SMT NUM CALLS - UNKNOWN] " + smtNumCallsUnknown); out.println("[SMT NUM CALLS - DOUBLE CHECK] " + smtNumCallsDoubleCheck); out.println("[SMT NUM CALLS - FALSE UNKNOWN] " + smtNumCallsFalseUnknown); if (csaEvalStats.size() > 0) { out.println("--- CSA ---"); out.printf("[CSA EVAL TIME] %1.1fms%n", csaEvalStats.computeSum() / 1e6); out.println("[CSA EVAL TIME PER CALL (ms)] " + csaEvalStats.getStatsString(1e-6)); out.println("[CSA CACHE LIMIT] " + smtCacheSize); out.println("[CSA CACHE BASE SIZE] " + csaCacheSize.getStatsString()); out.println("[CSA CACHE HITS] " + csaCacheHits.getStatsString()); out.println("[CSA CACHE MISSES] " + csaCacheMisses.getStatsString()); out.println("[CSA CACHE HIT RATE] " + csaCacheHitRate.getStatsString()); out.println("[CSA CACHE USE RATE] " + csaCacheUseRate.getStatsString()); out.println("[CSA CACHE CLEARS] " + csaCacheClears.get()); } if (pushPopEvalStats.size() > 0) { out.println("--- PUSH POP ---"); out.printf("[PUSH POP EVAL TIME] %1.1fms%n", pushPopEvalStats.computeSum() / 1e6); out.println("[PUSH POP EVAL TIME PER CALL (ms)] " + pushPopEvalStats.getStatsString(1e-6)); out.println("[PUSH POP STACK BASE SIZE] " + pushPopStackSize.getStatsString()); out.println("[PUSH POP STACK PUSHES] " + pushPopStackPushes.getStatsString()); out.println("[PUSH POP STACK POPS] " + pushPopStackPops.getStatsString()); out.println("[PUSH POP STACK DELTA] " + pushPopStackDelta.getStatsString()); out.println("[PUSH POP STACK REUSE] " + pushPopStackReuse.getStatsString()); } if (otherSolverEvalStats.size() > 0) { out.println("--- OTHER ---"); out.printf("[OTHER EVAL TIME] %1.1fms%n", otherSolverEvalStats.computeSum() / 1e6); out.println("[OTHER EVAL TIME PER CALL (ms)] " + otherSolverEvalStats.getStatsString(1e-6)); } } public static void recordPushPopSolverStats( int solverId, int stackStartSize, int pops, int pushes) { pushPopStackSize.addDataPoint(stackStartSize); pushPopStackReuse.addDataPoint(stackStartSize - pops); pushPopStackPops.addDataPoint(pops); pushPopStackPushes.addDataPoint(pushes); pushPopStackDelta.addDataPoint(pushes - pops); } public static void recordCsaCacheStats(int solverId, int hits, int misses, int oldSize) { int numAsserts = hits + misses; csaCacheHits.addDataPoint(hits); csaCacheMisses.addDataPoint(misses); csaCacheHitRate.addDataPoint(numAsserts == 0 ? 1 : (double) hits / numAsserts); csaCacheUseRate.addDataPoint(oldSize == 0 ? 1 : (double) hits / oldSize); csaCacheSize.addDataPoint(oldSize); } public static void recordSmtDoubleCheck(boolean falseUnknown) { smtNumCallsDoubleCheck.incrementAndGet(); if (falseUnknown) { smtNumCallsFalseUnknown.incrementAndGet(); } } public static void recordCsaCacheClear(int solverId) { csaCacheClears.incrementAndGet(); } public static void recordFuncTime(FunctionSymbol func, long time) { AtomicLong l = Util.lookupOrCreate(funcTimes, func, () -> new AtomicLong()); l.addAndGet(time); } public static Map getFuncDiagnostics() { return Collections.unmodifiableMap(funcTimes); } public static synchronized void printFuncDiagnostics(PrintStream out) { Map times = Configuration.getFuncDiagnostics(); List> sorted = times.entrySet().stream().sorted(sortTimes).collect(Collectors.toList()); Iterator> it = sorted.iterator(); int end = Math.min(times.size(), 10); for (int i = 0; i < end; ++i) { Map.Entry e = it.next(); out.println("[FUNC DIAGNOSTICS] " + e.getValue().get() + "ms: " + e.getKey()); } } private static final Comparator> sortTimes = new Comparator>() { @Override public int compare(Entry e1, Entry e2) { return -Long.compare(e1.getValue().get(), e2.getValue().get()); } }; private static final Comparator>> sortPairedTimes = new Comparator>>() { @Override public int compare( Entry> e1, Entry> e2) { return -Long.compare(getTotal(e1), getTotal(e2)); } private long getTotal(Entry> e) { Pair p = e.getValue(); return p.fst().get() + p.snd().get(); } }; public static void recordRulePrefixTime(Rule rule, long time) { Pair p = Util.lookupOrCreate(ruleTimes, rule, () -> new Pair<>(new AtomicLong(), new AtomicLong())); p.fst().addAndGet(time); } public static void recordRuleSuffixTime(Rule rule, long time) { Pair p = Util.lookupOrCreate(ruleTimes, rule, () -> new Pair<>(new AtomicLong(), new AtomicLong())); p.snd().addAndGet(time); } public static synchronized void printRuleDiagnostics(PrintStream out) { Map, Pair> times = ruleTimes; List, Pair>> sorted = times.entrySet().stream().sorted(sortPairedTimes).collect(Collectors.toList()); Iterator, Pair>> it = sorted.iterator(); int end = Math.min(times.size(), 10); for (int i = 0; i < end; ++i) { Map.Entry, Pair> e = it.next(); Pair p = e.getValue(); long pre = p.fst().get(); long suf = p.snd().get(); long total = pre + suf; out.println( "[RULE DIAGNOSTICS] " + total + " (" + pre + " + " + suf + ") ms:\n" + e.getKey()); } } public static synchronized void printRelSizes( PrintStream out, String header, IndexedFactDb db, boolean printEmpty) { for (RelationSymbol sym : db.getSymbols()) { if (printEmpty || !db.isEmpty(sym)) { out.println( "[" + header + "] " + sym + ": " + db.countDistinct(sym) + " / " + db.numIndices(sym) + " / " + db.countDuplicates(sym)); } } } private static boolean propIsSet(String prop, boolean defaultValue) { String val = System.getProperty(prop); if (val == null) { return defaultValue; } if (val.equals("true") || val.equals("")) { return true; } if (val.equals("false")) { return false; } throw new IllegalArgumentException("Unexpected argument for property " + prop + ": " + val); } private static boolean propIsSet(String prop) { return propIsSet(prop, false); } static int getIntProp(String prop, int def) { String val = System.getProperty(prop); if (val == null) { return def; } try { return Integer.parseInt(val); } catch (NumberFormatException e) { throw new IllegalArgumentException("Property " + prop + " expects an integer argument"); } } private static String getStringProp(String prop, String def) { String val = System.getProperty(prop); if (val == null) { return def; } return val; } static List getListProp(String prop) { String val = System.getProperty(prop); if (val == null || val.equals("")) { return Collections.emptyList(); } List l = new ArrayList<>(); breakIntoCollection(val, l); return Collections.unmodifiableList(l); } private static void breakIntoCollection(String s, Collection acc) { int split; while ((split = s.indexOf(',')) != -1) { String sub = s.substring(0, split); acc.add(sub); if (split == s.length()) { return; } s = s.substring(split + 1); } acc.add(s); } static SmtStrategy getSmtStrategy() { String val = System.getProperty("smtStrategy"); if (val == null) { val = "queue-1"; } switch (val) { case "naive": return new SmtStrategy(SmtStrategy.Tag.NAIVE, null); case "pushPop": return new SmtStrategy(SmtStrategy.Tag.PUSH_POP, null); case "pushPopNaive": return new SmtStrategy(SmtStrategy.Tag.PUSH_POP_NAIVE, null); case "perThreadNaive": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_NAIVE, null); case "perThreadPushPop": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_PUSH_POP, null); case "perThreadPushPopNaive": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_PUSH_POP_NAIVE, null); } Pattern p = Pattern.compile("queue-(\\d+)"); Matcher m = p.matcher(val); if (m.matches()) { int size = Integer.parseInt(m.group(1)); return new SmtStrategy(SmtStrategy.Tag.QUEUE, size); } p = Pattern.compile("bestMatch-(\\d+)"); m = p.matcher(val); if (m.matches()) { int size = Integer.parseInt(m.group(1)); return new SmtStrategy(SmtStrategy.Tag.BEST_MATCH, size); } p = Pattern.compile("perThreadQueue-(\\d+)"); m = p.matcher(val); if (m.matches()) { int size = Integer.parseInt(m.group(1)); return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_QUEUE, size); } p = Pattern.compile("perThreadBestMatch-(\\d+)"); m = p.matcher(val); if (m.matches()) { int size = Integer.parseInt(m.group(1)); return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_BEST_MATCH, size); } throw new IllegalArgumentException("Unrecognized SMT strategy: " + val); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/FormulogTester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2021-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.eval.EvaluationResult; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; import edu.harvard.seas.pl.formulog.functions.DummyFunctionDef; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.parsing.ParseException; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.TypeException; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.util.sexp.SExp; import edu.harvard.seas.pl.formulog.util.sexp.SExpException; import edu.harvard.seas.pl.formulog.util.sexp.SExpParser; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; public class FormulogTester { private boolean initialized; private final List tests = new ArrayList<>(); private WellTypedProgram prog; public synchronized void setup(Reader program, File testFile) throws ParseException, TypeException, IOException { Parser parser = new Parser(); prog = new TypeChecker(parser.parse(program)).typeCheck(); loadTests(testFile, parser); initialized = true; } private void loadTests(File testFile, Parser parser) throws IOException, ParseException { tests.clear(); ObjectMapper objectMapper = new ObjectMapper(); JsonNode topLevel = objectMapper.readValue(testFile, JsonNode.class); Path rootTestDir = testFile.getParentFile().toPath(); for (JsonNode test : topLevel) { tests.add(loadTest(test, rootTestDir, parser)); } } private FormulogTest loadTest(JsonNode test, Path rootTestDir, Parser parser) throws ParseException, FileNotFoundException { String name = test.get("name").asText(); Path dir = rootTestDir.resolve(name); Map> m = new HashMap<>(); for (RelationSymbol sym : prog.getFactSymbols()) { if (sym.isEdbSymbol()) { FileReader fr = new FileReader(dir.resolve(sym + ".tsv").toFile()); Set s = parser.parseFacts(sym, fr); m.put(sym, s); } } try { Function> logic = parseBoolLogic(test.get("spec").asText()); return new FormulogTest(name, m, logic); } catch (ParseException e) { throw new ParseException(-1, "Trouble parsing spec for test " + name + ": " + e.getMessage()); } } private Function> parseBoolLogic(String s) throws ParseException { Reader r = new StringReader(s); SExpParser p = new SExpParser(r); SExp sexp; try { sexp = p.parse(); } catch (SExpException e) { throw new ParseException(-1, e.getMessage()); } return parseBoolLogic(sexp); } private Function> parseBoolLogic(SExp sexp) throws ParseException { if (sexp.isAtom()) { return parseBoolAtom(sexp.asAtom()); } else { return parseBoolList(sexp.asList()); } } private Function> parseBoolAtom(String atom) throws ParseException { switch (atom) { case "true": return r -> Collections.emptyList(); default: throw new ParseException(-1, "Unrecognized boolean constant: " + atom); } } private Function> parseBoolList(List l) throws ParseException { if (l.isEmpty()) { throw new ParseException(-1, "Unexpected empty list"); } SExp head = l.get(0); if (head.isList()) { throw new ParseException(-1, "Expected an operator, but got a list: " + head); } String cmd = head.asAtom(); switch (cmd) { case "=": { if (l.size() != 3) { throw new ParseException(-1, "Wrong number of arguments for = operator: " + l.size()); } SExp sexp1 = l.get(1); SExp sexp2 = l.get(2); Function f1 = parseIntLogic(sexp1); Function f2 = parseIntLogic(sexp2); return r -> { int v1 = f1.apply(r); int v2 = f2.apply(r); if (v1 == v2) { return Collections.emptyList(); } else { return Collections.singletonList( "Failed equality: (= " + sexp1 + " " + sexp2 + ") reduces to (= " + v1 + " " + v2 + ")"); } }; } case "and": { List>> fs = new ArrayList<>(); for (Iterator it = l.listIterator(1); it.hasNext(); ) { fs.add(parseBoolLogic(it.next())); } return r -> { List errors = new ArrayList<>(); for (Function> f : fs) { errors.addAll(f.apply(r)); } return errors; }; } default: throw new ParseException(-1, "Unrecognized boolean operator: " + cmd); } } private Function parseIntLogic(SExp sexp) throws ParseException { if (sexp.isAtom()) { return parseIntAtom(sexp.asAtom()); } else { return parseIntList(sexp.asList()); } } private Function parseIntAtom(String s) throws ParseException { try { int n = Integer.parseInt(s); return r -> n; } catch (NumberFormatException e) { throw new ParseException(-1, "Expected an integer constant but got " + s); } } private Function parseIntList(List l) throws ParseException { if (l.isEmpty()) { throw new ParseException(-1, "Unexpected empty list"); } SExp head = l.get(0); if (head.isList()) { throw new ParseException(-1, "Expected an operator, but got a list: " + head); } String cmd = head.asAtom(); switch (cmd) { case "size": if (l.size() != 2) { throw new ParseException(-1, "Wrong number of arguments for size operator: " + l.size()); } SExp sexp1 = l.get(1); if (sexp1.isList()) { throw new ParseException( -1, "size operator expects a relation symbol, but got a list: " + sexp1); } String name = sexp1.asAtom(); SymbolManager sm = prog.getSymbolManager(); if (!sm.hasName(name)) { throw new ParseException(-1, "unrecognized relation used in size operator: " + name); } Symbol sym = sm.lookupSymbol(name); if (!(sym instanceof RelationSymbol)) { throw new ParseException(-1, "non-relation symbol used in size operator: " + name); } RelationSymbol rel = (RelationSymbol) sym; return r -> r.getCount(rel); default: throw new ParseException(-1, "Unrecognized integer operator: " + cmd); } } synchronized boolean runTests() throws InvalidProgramException, EvaluationException { if (!initialized) { throw new IllegalStateException("Need to set up tests first."); } // Need to get the EDBs that are hard-coded into the program. Map> hardCodedFacts = new HashMap<>(); for (RelationSymbol sym : prog.getFactSymbols()) { hardCodedFacts.put(sym, new HashSet<>(prog.getFacts(sym))); } boolean ok = true; for (FormulogTest test : tests) { ok &= runTest(test, hardCodedFacts); } return ok; } private void clearCachedState() { // Need to clear memoized function calls prog.getFunctionCallFactory().clearMemoCache(); // Need to reset predicate functions for (FunctionSymbol sym : prog.getFunctionSymbols()) { FunctionDef def = prog.getDef(sym); if (def instanceof DummyFunctionDef) { ((DummyFunctionDef) def).setDef(null); } } } private boolean runTest(FormulogTest test, Map> hardCodedFacts) throws InvalidProgramException, EvaluationException { for (RelationSymbol sym : prog.getFactSymbols()) { if (sym.isEdbSymbol()) { Set s = prog.getFacts(sym); s.clear(); s.addAll(hardCodedFacts.get(sym)); s.addAll(test.testInputs.get(sym)); } } clearCachedState(); // This would be better if we could set up the program, and then load // the external facts, so we do not need to do the setup each time. Evaluation e = SemiNaiveEvaluation.setup(prog, 1, false); System.out.print(test.name + "... "); e.run(); List errors = test.testLogic.apply(e.getResult()); if (errors.isEmpty()) { System.out.println("PASSED"); return true; } else { System.out.println("FAILED"); for (String error : errors) { System.out.println(">>> " + error); } return false; } } private static class FormulogTest { public final String name; public final Map> testInputs; public final Function> testLogic; public FormulogTest( String name, Map> testInputs, Function> testLogic) { this.name = name; this.testInputs = testInputs; this.testLogic = testLogic; } } public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("Expected a single Formulog file as an input."); System.exit(1); } if (Configuration.testFile == null) { System.out.println("Must specify a JSON test file."); System.exit(1); } main(new File(args[0]), new File(Configuration.testFile)); } public static void main(File file, File json) throws Exception { FormulogTester tester = new FormulogTester(); tester.setup(new FileReader(file), json); System.exit(tester.runTests() ? 0 : 1); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/Main.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.codegen.CodeGen; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.eval.EvaluationResult; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; import edu.harvard.seas.pl.formulog.parsing.ParseException; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.smt.AbstractSmtLibSolver; import edu.harvard.seas.pl.formulog.smt.SmtStrategy; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.TypeException; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.lang3.time.StopWatch; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Model; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; import picocli.CommandLine.Parameters; import picocli.CommandLine.Spec; @Command( name = "formulog", mixinStandardHelpOptions = true, version = "Formulog 0.8.0", description = "Runs Formulog.") public final class Main implements Callable { @Spec private static Model.CommandSpec spec; @Option( names = "--smt-solver-mode", description = "Strategy to use when interacting with external SMT solvers" + "('naive', 'push-pop', or 'check-sat-assuming').", converter = SmtModeConverter.class) public static SmtStrategy smtStrategy = Configuration.getSmtStrategy(); @Option( names = "--eager-eval", description = "Use eager evaluation (instead of traditional semi-naive Datalog evaluation)") public static boolean eagerEval = Configuration.eagerSemiNaive; @Option( names = {"-F", "--fact-dir"}, description = "Directory to look for fact .tsv files (default: '.').") private List factDirs = Configuration.getListProp("factDirs"); @Option( names = {"-D", "--output-dir"}, description = "Directory for .tsv output files (default: '.').") private File outDir = new File("."); @Option( names = {"-j", "--parallelism"}, description = "Number of threads to use.") public static int parallelism = Configuration.getIntProp("parallelism", 4); @Option(names = "--dump-all", description = "Print all relations.") private boolean dumpAll; @Option(names = "--dump-idb", description = "Print all IDB relations.") private boolean dumpIdb; @Option(names = "--dump", description = "Print selected relations.") private Set relationsToPrint = Collections.emptySet(); @Option(names = "--dump-query", description = "Print query result.") private boolean dumpQuery; @Option(names = "--dump-sizes", description = "Print relation sizes.") private boolean dumpSizes; @Option( names = {"-c", "--codegen"}, description = "Compile the Formulog program.") private boolean codegen; @Option( names = {"--codegen-dir"}, description = "Directory for generated code (default: './codegen').") private File codegenDir = new File("codegen"); @Option( names = {"--smt-stats"}, description = "Report basic statistics related to SMT solver usage.") public static boolean smtStats = false; @Parameters(index = "0", description = "Formulog program file.") private File file; private final StopWatch clock = new StopWatch(); private volatile boolean interrupted = true; private static final boolean exnStackTrace = System.getProperty("exnStackTrace") != null; private void go() { if (!outDir.mkdirs() && !outDir.exists() || !outDir.isDirectory()) { System.out.println("Unable to create output directory: " + outDir); System.exit(1); } BasicProgram prog = parse(); WellTypedProgram typedProg = typeCheck(prog); Evaluation eval = setup(typedProg); Runtime.getRuntime() .addShutdownHook( new Thread( () -> { if (interrupted) { dumpResults(eval.getResult()); } })); evaluate(eval); interrupted = false; var res = eval.getResult(); dumpResults(res); dumpResultsToDisk(res); AbstractSmtLibSolver.destroyAll(); } private BasicProgram parse() { System.out.println("Parsing..."); clock.start(); try { List factPaths = factDirs.stream().map(Paths::get).collect(Collectors.toList()); if (factPaths.isEmpty()) { factPaths = Collections.singletonList(Paths.get("")); } FileReader reader = new FileReader(file); BasicProgram prog = new Parser().parse(reader, factPaths); clock.stop(); System.out.println("Finished parsing (" + clock.getTime() / 1000.0 + "s)"); return prog; } catch (FileNotFoundException e) { handleException("Error while parsing!", e); } catch (ParseException e) { String msg = "Error while parsing "; if (e.getFileName() != null) { msg += e.getFileName() + ", "; } msg += "line " + e.getLineNo() + ":"; handleException(msg, e); } throw new AssertionError("impossible"); } private WellTypedProgram typeCheck(Program prog) { System.out.println("Type checking..."); clock.reset(); clock.start(); try { WellTypedProgram prog2 = new TypeChecker(prog).typeCheck(); clock.stop(); System.out.println("Finished type checking (" + clock.getTime() / 1000.0 + "s)"); return prog2; } catch (TypeException e) { handleException("Error while typechecking the program!", e); throw new AssertionError("impossible"); } } private Evaluation setup(WellTypedProgram prog) { System.out.println("Rewriting and validating..."); clock.reset(); clock.start(); try { Evaluation eval = SemiNaiveEvaluation.setup(prog, parallelism, eagerEval); clock.stop(); System.out.println("Finished rewriting and validating (" + clock.getTime() / 1000.0 + "s)"); return eval; } catch (InvalidProgramException e) { handleException("Error while rewriting/validation!", e); throw new AssertionError("impossible"); } } private void evaluate(Evaluation eval) { System.out.println("Evaluating..."); clock.reset(); clock.start(); try { eval.run(); clock.stop(); System.out.println("Finished evaluating (" + clock.getTime() / 1000.0 + "s)"); } catch (EvaluationException e) { handleException("Error while evaluating the program!", e); } } private String getBanner(String heading) { return "==================== " + heading + " ===================="; } private String getSmallBanner(String heading) { return "---------- " + heading + " ----------"; } private static class FactFileDumper implements Runnable { private final Iterable facts; private final PrintStream out; public FactFileDumper(Iterable facts, PrintStream out) { this.facts = facts; this.out = out; } @Override public void run() { for (var fact : facts) { Term[] args = fact.getArgs(); for (int i = 0; i < args.length; ++i) { out.print(args[i]); if (i < args.length - 1) { out.print("\t"); } } out.println(); } out.close(); } } private void dumpResultsToDisk(EvaluationResult res) { var pool = Executors.newFixedThreadPool(parallelism); try { for (RelationSymbol sym : res.getSymbols()) { if (sym.isIdbSymbol() && sym.isDisk()) { File outFile = outDir.toPath().resolve(sym + ".tsv").toFile(); boolean ok = outFile.createNewFile(); if (!ok && !outFile.exists()) { throw new IOException("Cannot create output file " + outFile.getName()); } pool.submit(new FactFileDumper(res.getAll(sym), new PrintStream(outFile))); } } pool.shutdown(); while (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)) { // do nothing } } catch (Exception e) { handleException("Problem writing output to disk", e); } } private void printSmtStats(PrintStream out) { List times = new ArrayList<>(); List calls = new ArrayList<>(); long[] total_time = {0}; long[] total_calls = {0}; Configuration.smtTime.forEach( stats -> { times.add(Long.toString(stats.time)); total_time[0] += stats.time; calls.add(Long.toString(stats.ncalls)); total_calls[0] += stats.ncalls; }); out.println(); out.println(getBanner("SMT STATS")); out.println("SMT calls: " + total_calls[0]); out.println("SMT time (ms): " + total_time[0]); out.println("SMT wait time (ms): " + Configuration.smtWaitTime.unsafeGet() / 1e6); out.println("SMT cache hits: " + Configuration.smtCacheHits.unsafeGet()); out.println("SMT cache misses: " + Configuration.smtCacheMisses.unsafeGet()); out.println("SMT cache clears: " + Configuration.smtCacheClears.unsafeGet()); out.println("SMT calls per solver: " + String.join(",", calls)); out.println("SMT time per solver (ms): " + String.join(",", times)); } private void dumpResults(EvaluationResult res) { PrintStream out = System.out; if (smtStats) { printSmtStats(out); } List allSymbols = res.getSymbols().stream() .sorted(Comparator.comparing(Object::toString)) .collect(Collectors.toList()); Set stringReprs = allSymbols.stream().map(RelationSymbol::toString).collect(Collectors.toSet()); for (String sym : relationsToPrint) { if (!stringReprs.contains(sym)) { out.println("\nWARNING: ignoring unrecognized relation " + sym); } } if (dumpSizes) { out.println(); out.println(getBanner("RELATION SIZES")); for (RelationSymbol sym : allSymbols) { out.println(sym + ": " + res.getCount(sym)); } } if (dumpAll || dumpQuery) { var queryRes = res.getQueryAnswer(); if (queryRes != null) { List l = new ArrayList<>(); queryRes.forEach(l::add); out.println(); out.println(getBanner("QUERY RESULTS (" + l.size() + ")")); Util.printSortedFacts(l, out); } } List edbSymbols = allSymbols.stream() .filter( sym -> sym.isEdbSymbol() && (dumpAll || relationsToPrint.contains(sym.toString()))) .collect(Collectors.toList()); dumpRelations("SELECTED EDB RELATIONS", edbSymbols, res, out); List idbSymbols = allSymbols.stream() .filter( sym -> sym.isIdbSymbol() && (dumpAll || dumpIdb || relationsToPrint.contains(sym.toString()))) .collect(Collectors.toList()); dumpRelations("SELECTED IDB RELATIONS", idbSymbols, res, out); } private void dumpRelations( String heading, Collection symbols, EvaluationResult res, PrintStream out) { if (symbols.isEmpty()) { return; } out.println(); out.println(getBanner(heading)); for (RelationSymbol sym : symbols) { out.println(); out.println(getSmallBanner(sym + " (" + res.getCount(sym) + ")")); if (res.getCount(sym) < 1000) { Util.printSortedFacts(res.getAll(sym), out); } else { for (var tup : res.getAll(sym)) { out.println(tup); } } } } public static class SmtModeConverter implements ITypeConverter { @Override public SmtStrategy convert(String s) throws Exception { switch (s) { case "push-pop": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_PUSH_POP, null); case "naive": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_PUSH_POP_NAIVE, null); case "check-sat-assuming": return new SmtStrategy(SmtStrategy.Tag.PER_THREAD_QUEUE, 1); } throw new ParameterException( Main.spec.commandLine(), "Unexpected value for SMT solver mode: " + s + " (must be one of 'naive', 'push-pop', or 'check-sat-assuming')"); } } public static void main(String[] args) throws Exception { int exitCode = new CommandLine(new Main()).execute(args); System.exit(exitCode); } private static void handleException(String msg, Exception e) { System.out.println(msg); System.out.println(e.getMessage()); if (exnStackTrace) { e.printStackTrace(System.out); } System.exit(1); } @Override public Integer call() throws Exception { if (codegen) { CodeGen.main(file, codegenDir); } else { go(); } return 0; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/PrintPreference.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog; public enum PrintPreference { EDB, IDB, NONE, ALL, QUERY, SOME } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/AbstractRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import java.util.Iterator; import java.util.List; public abstract class AbstractRule implements Rule { private final H head; private final List body; protected AbstractRule(H head, List body) { this.head = head; this.body = body; } @Override public Iterator iterator() { return body.iterator(); } @Override public H getHead() { return head; } @Override public int getBodySize() { return body.size(); } @Override public B getBody(int idx) { return body.get(idx); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(head); sb.append(" :-"); if (body.size() == 1) { sb.append(" "); } else { sb.append("\n\t"); } for (Iterator it = body.iterator(); it.hasNext(); ) { sb.append(it.next()); if (it.hasNext()) { sb.append(",\n\t"); } } sb.append("."); return sb.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((body == null) ? 0 : body.hashCode()); result = prime * result + ((head == null) ? 0 : head.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AbstractRule other = (AbstractRule) obj; if (body == null) { if (other.body != null) return false; } else if (!body.equals(other.body)) return false; if (head == null) { if (other.head != null) return false; } else if (!head.equals(other.head)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/AbstractTerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; public abstract class AbstractTerm implements Term { private final int id; public AbstractTerm() { this.id = Terms.nextId(); } @Override public int getId() { return id; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/BasicProgram.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; public interface BasicProgram extends Program {} ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/BasicRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import java.util.Collections; import java.util.List; public class BasicRule extends AbstractRule { private BasicRule(UserPredicate head, List body) { super(head, body); if (!head.getSymbol().isIdbSymbol()) { throw new IllegalArgumentException( "Cannot create rule with non-IDB predicate for head: " + head.getSymbol()); } } public static BasicRule make(UserPredicate head, List body) { if (body.isEmpty()) { return make(head); } return new BasicRule(head, body); } public static BasicRule make(UserPredicate head) { return make(head, Collections.singletonList(ComplexLiterals.trueAtom())); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/BindingType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; public enum BindingType { BOUND, FREE, IGNORED; public boolean isBound() { return this.equals(BOUND); } public boolean isFree() { return this.equals(FREE); } public boolean isIgnored() { return this.equals(IGNORED); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/BoolTerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.util.Collections; import java.util.Set; import org.pcollections.PMap; public class BoolTerm extends AbstractTerm implements Primitive, SmtLibTerm { private final boolean val; private static final BoolTerm true_ = new BoolTerm(true); private static final BoolTerm false_ = new BoolTerm(false); private BoolTerm(boolean val) { this.val = val; } public static BoolTerm mk(boolean val) { return val ? true_ : false_; } public static BoolTerm mkTrue() { return true_; } public static BoolTerm mkFalse() { return false_; } @Override public Boolean getVal() { return val; } @Override public Type getType() { return BuiltInTypes.bool; } @Override public void toSmtLib(SmtLibShim shim) { shim.print(this.toString()); } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public Set freeVars() { return Collections.emptySet(); } @Override public String toString() { return Boolean.toString(val); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/ComplexLiteral.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.HashSet; import java.util.Set; public interface ComplexLiteral extends Literal { Term[] getArgs(); ComplexLiteral applySubstitution(Substitution subst); boolean isNegated(); default Set varSet() { Set vars = new HashSet<>(); for (Term arg : getArgs()) { arg.varSet(vars); } return vars; } O accept(ComplexLiteralVisitor visitor, I input); O accept(ComplexLiteralExnVisitor visitor, I input) throws E; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/ComplexLiterals.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; public final class ComplexLiterals { private ComplexLiterals() { throw new AssertionError("impossible"); } public static UnificationPredicate unifyWithBool(Term t, boolean bool) { return UnificationPredicate.make(t, bool ? BoolTerm.mkTrue() : BoolTerm.mkFalse(), false); } public static UnificationPredicate trueAtom() { return unifyWithBool(BoolTerm.mkTrue(), true); } public static interface ComplexLiteralVisitor { O visit(UnificationPredicate unificationPredicate, I input); O visit(UserPredicate userPredicate, I input); } public static interface ComplexLiteralExnVisitor { O visit(UnificationPredicate unificationPredicate, I input) throws E; O visit(UserPredicate userPredicate, I input) throws E; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Constructor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; public interface Constructor extends Functor, SmtLibTerm { @Override default O accept(TermVisitor v, I in) { return v.visit(this, in); } @Override default O accept(TermVisitorExn v, I in) throws E { return v.visit(this, in); } @Override default Term applySubstitution(Substitution s) { if (isGround()) { return this; } Term[] args = getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].applySubstitution(s); } return copyWithNewArgs(newArgs); } @Override Term copyWithNewArgs(Term[] newArgs); @Override default Term normalize(Substitution s) throws EvaluationException { if (isGround() && !containsUnevaluatedTerm()) { return this; } Term[] args = getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].normalize(s); } return copyWithNewArgs(newArgs); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Constructors.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager.TupleSymbol; import edu.harvard.seas.pl.formulog.symbols.RecordSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.util.FunctorUtil; import edu.harvard.seas.pl.formulog.util.FunctorUtil.Memoizer; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.pcollections.PMap; public final class Constructors { private Constructors() { throw new AssertionError(); } private static final Memoizer memo = new Memoizer<>(); public static Constructor make(ConstructorSymbol sym, Term[] args) { assert sym.getArity() == args.length : sym + " " + Arrays.toString(args); if (sym instanceof BuiltInConstructorSymbol) { return lookupOrCreateBuiltInConstructor((BuiltInConstructorSymbol) sym, args); } if (sym instanceof ParameterizedConstructorSymbol) { return lookupOrCreateParameterizedConstructor((ParameterizedConstructorSymbol) sym, args); } if (sym instanceof TupleSymbol) { return memo.lookupOrCreate(sym, args, () -> new Tuple((TupleSymbol) sym, args)); } if (sym instanceof RecordSymbol) { return memo.lookupOrCreate(sym, args, () -> new Record((RecordSymbol) sym, args)); } switch (sym.getConstructorSymbolType()) { case SOLVER_UNINTERPRETED_FUNCTION: return memo.lookupOrCreate(sym, args, () -> new SolverUninterpretedFunction(sym, args)); case SOLVER_CONSTRUCTOR_TESTER: return memo.lookupOrCreate(sym, args, () -> makeConstructorTester(sym, args)); case SOLVER_CONSTRUCTOR_GETTER: return memo.lookupOrCreate(sym, args, () -> makeConstructorGetter(sym, args)); case INDEX_CONSTRUCTOR: case VANILLA_CONSTRUCTOR: return memo.lookupOrCreate(sym, args, () -> new VanillaConstructor(sym, args)); default: throw new IllegalArgumentException( "Cannot create constructor for non-constructor symbol " + sym + "."); } } public static Constructor makeZeroAry(ConstructorSymbol sym) { return make(sym, Terms.emptyArray()); } private static final Constructor nil = makeZeroAry(BuiltInConstructorSymbol.NIL); public static Constructor nil() { return nil; } public static Constructor cons(Term hd, Term tl) { return make(BuiltInConstructorSymbol.CONS, new Term[] {hd, tl}); } private static final Constructor none = makeZeroAry(BuiltInConstructorSymbol.NONE); public static final Term none() { return none; } public static Term some(Term arg) { return make(BuiltInConstructorSymbol.SOME, Terms.singletonArray(arg)); } public static Term tuple(Term... args) { return make(GlobalSymbolManager.lookupTupleSymbol(args.length), args); } private static Constructor lookupOrCreateBuiltInConstructor( BuiltInConstructorSymbol sym, Term[] args) { Function makeSolverOp = op -> memo.lookupOrCreate(sym, args, () -> new SolverOperation(sym, args, op)); switch (sym) { case NIL: return makeNil(sym, args); case CONS: return makeCons(sym, args); case CMP_EQ: case CMP_GT: case CMP_LT: case NONE: case SOME: return memo.lookupOrCreate(sym, args, () -> new VanillaConstructor(sym, args)); case SMT_AND: return makeAnd(args); case SMT_IMP: return makeSolverOp.apply("=>"); case SMT_ITE: return memo.lookupOrCreate(sym, args, () -> new SolverIte(sym, args)); case SMT_NOT: return makeSolverOp.apply("not"); case SMT_OR: return makeOr(args); case SMT_EXISTS: case SMT_FORALL: return memo.lookupOrCreate(sym, args, () -> new Quantifier(sym, args)); case BV_ADD: return makeSolverOp.apply("bvadd"); case BV_AND: return makeSolverOp.apply("bvand"); case BV_MUL: return makeSolverOp.apply("bvmul"); case BV_NEG: return makeSolverOp.apply("bvneg"); case BV_OR: return makeSolverOp.apply("bvor"); case BV_SDIV: return makeSolverOp.apply("bvsdiv"); case BV_UDIV: return makeSolverOp.apply("bvudiv"); case BV_SREM: return makeSolverOp.apply("bvsrem"); case BV_UREM: return makeSolverOp.apply("bvurem"); case BV_SUB: return makeSolverOp.apply("bvsub"); case BV_XOR: return makeSolverOp.apply("bvxor"); case BV_SHL: return makeSolverOp.apply("bvshl"); case BV_ASHR: return makeSolverOp.apply("bvashr"); case BV_LSHR: return makeSolverOp.apply("bvlshr"); case FP_ADD: return makeSolverOp.apply("fp.add"); case FP_DIV: return makeSolverOp.apply("fp.div"); case FP_MUL: return makeSolverOp.apply("fp.mul"); case FP_NEG: return makeSolverOp.apply("fp.neg"); case FP_REM: return makeSolverOp.apply("fp.rem"); case FP_SUB: return makeSolverOp.apply("fp.sub"); case ARRAY_STORE: return makeSolverOp.apply("store"); case ARRAY_CONST: return makeSolverOp.apply("const"); case INT_ABS: return makeSolverOp.apply("abs"); case INT_ADD: return makeSolverOp.apply("+"); case INT_BIG_CONST: case INT_CONST: return makeIntConst(sym, args); case INT_DIV: return makeSolverOp.apply("div"); case INT_GE: return makeSolverOp.apply(">="); case INT_GT: return makeSolverOp.apply(">"); case INT_LE: return makeSolverOp.apply("<="); case INT_LT: return makeSolverOp.apply("<"); case INT_MUL: return makeSolverOp.apply("*"); case INT_NEG: return makeSolverOp.apply("-"); case INT_MOD: return makeSolverOp.apply("mod"); case INT_SUB: return makeSolverOp.apply("-"); case STR_AT: return makeSolverOp.apply("str.at"); case STR_CONCAT: return makeSolverOp.apply("str.++"); case STR_CONTAINS: return makeSolverOp.apply("str.contains"); case STR_INDEXOF: return makeSolverOp.apply("str.indexof"); case STR_LEN: return makeSolverOp.apply("str.len"); case STR_PREFIXOF: return makeSolverOp.apply("str.prefixof"); case STR_REPLACE: return makeSolverOp.apply("str.replace"); case STR_SUBSTR: return makeSolverOp.apply("str.substr"); case STR_SUFFIXOF: return makeSolverOp.apply("str.suffixof"); case ENTER_FORMULA: return memo.lookupOrCreate(sym, args, () -> new VanillaConstructor(sym, args)); case EXIT_FORMULA: return memo.lookupOrCreate(sym, args, () -> new VanillaConstructor(sym, args)); } throw new AssertionError("impossible"); } private static Constructor makeAnd(Term[] args) { ConstructorSymbol sym = BuiltInConstructorSymbol.SMT_AND; return memo.lookupOrCreate( sym, args, () -> { return new SolverOperation(sym, args, "and"); }); } private static Constructor makeOr(Term[] args) { ConstructorSymbol sym = BuiltInConstructorSymbol.SMT_OR; return memo.lookupOrCreate( sym, args, () -> { return new SolverOperation(sym, args, "or"); }); } // Used for renaming variables to avoid capture. private static final Map, SolverVariable> binderMemo = new ConcurrentHashMap<>(); private static final AtomicInteger cnt = new AtomicInteger(); private static SolverVariable renameBinder(SolverVariable x) { ParameterizedConstructorSymbol sym = x.getSymbol(); sym = sym.copyWithNewArgs(Param.wildCard(), sym.getArgs().get(1)); return (SolverVariable) make(sym, Terms.singletonArray(Terms.makeDummyTerm(cnt.getAndIncrement()))); } private static class SolverLet extends AbstractConstructor { public static Constructor make(ConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> { return new SolverLet(sym, args); }); } private SolverLet(ConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { shim.print("(let (("); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(" "); ((SmtLibTerm) args[1]).toSmtLib(shim); shim.print(")) "); ((SmtLibTerm) args[2]).toSmtLib(shim); shim.print(")"); } @Override public SmtLibTerm substSolverTerms(PMap subst) { // Rename bound variable to avoid variable capture. SolverVariable x = (SolverVariable) args[0]; SolverVariable y = Util.lookupOrCreate(binderMemo, subst, () -> renameBinder(x)); Term[] newArgs = new Term[args.length]; newArgs[0] = y; newArgs[1] = ((SmtLibTerm) args[1]).substSolverTerms(subst); newArgs[2] = ((SmtLibTerm) args[2]).substSolverTerms(subst.plus(x, y)); return (SmtLibTerm) copyWithNewArgs(newArgs); } @Override public String toString() { return "(#let " + args[0] + " = " + args[1] + " in " + args[2] + ")"; } @Override public Set freeVars() { Set vars = new HashSet<>(((SmtLibTerm) args[2]).freeVars()); vars.remove((SolverVariable) args[0]); vars.addAll(((SmtLibTerm) args[1]).freeVars()); return vars; } } private static class Quantifier extends AbstractConstructor { protected Quantifier(BuiltInConstructorSymbol sym, Term[] args) { super(sym, args); } private boolean isExists() { return sym.equals(BuiltInConstructorSymbol.SMT_EXISTS); } @Override public void toSmtLib(SmtLibShim shim) { String quantifier = "forall ("; if (isExists()) { quantifier = "exists ("; } shim.print("("); shim.print(quantifier); for (Term t : getBoundVars()) { shim.getTypeAnnotation(BuiltInConstructorSymbol.CONS); SolverVariable x = (SolverVariable) t; shim.print("("); x.toSmtLib(shim); shim.print(" "); FunctorType ft = (FunctorType) x.getSymbol().getCompileTimeType(); shim.print(ft.getRetType()); shim.print(")"); } shim.print(") "); // Consume final type annotation for variable list shim.getTypeAnnotation(BuiltInConstructorSymbol.NIL); List> pats = getPatterns(); // XXX Need to check if pattern is valid! if (!pats.isEmpty()) { shim.print("(! "); } ((SmtLibTerm) args[1]).toSmtLib(shim); if (!pats.isEmpty()) { for (List pat : pats) { shim.print(" :pattern ("); shim.getTypeAnnotation(BuiltInConstructorSymbol.CONS); for (Iterator it = pat.iterator(); it.hasNext(); ) { shim.getTypeAnnotation(BuiltInConstructorSymbol.CONS); Constructor wrappedPat = (Constructor) it.next(); SmtLibTerm t = (SmtLibTerm) wrappedPat.getArgs()[0]; t.toSmtLib(shim); if (it.hasNext()) { shim.print(" "); } } shim.print(")"); // Consume final type annotation for pattern list shim.getTypeAnnotation(BuiltInConstructorSymbol.NIL); } shim.print(")"); } // Consume final type annotation for pattern list list shim.getTypeAnnotation(BuiltInConstructorSymbol.NIL); shim.print(")"); } @Override public SmtLibTerm substSolverTerms(PMap subst) { // Rename bound variable to avoid variable capture. List newVars = new ArrayList<>(); PMap newSubst = subst; for (Term t : getBoundVars()) { SolverVariable x = (SolverVariable) t; SolverVariable y = Util.lookupOrCreate(binderMemo, subst, () -> renameBinder(x)); newVars.add(y); newSubst = subst.plus(x, y); } Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = ((SmtLibTerm) args[i]).substSolverTerms(newSubst); } return (SmtLibTerm) copyWithNewArgs(newArgs); } protected List> getPatterns() { List> l = new ArrayList<>(); for (Term pat : Terms.termToTermList(args[2])) { l.add(Terms.termToTermList(pat)); } return l; } private List getBoundVars() { List wrappedVars = Terms.termToTermList(args[0]); List vars = new ArrayList<>(); for (Term wrappedVar : wrappedVars) { SolverVariable var = (SolverVariable) ((Constructor) wrappedVar).getArgs()[0]; vars.add(var); } return vars; } @Override public Set freeVars() { Set vars = super.freeVars(); vars.removeAll(getBoundVars()); return vars; } } private static class Nil extends AbstractConstructor { protected Nil(ConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } @Override public String toString() { return "[]"; } } private static class Cons extends AbstractConstructor { protected Cons(ConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } @Override public String toString() { List listArgs = new ArrayList<>(); Term cur = this; while (cur instanceof Cons) { Cons cons = (Cons) cur; listArgs.add(cons.args[0]); cur = cons.args[1]; } if (cur instanceof Nil) { String s = "["; for (Iterator it = listArgs.iterator(); it.hasNext(); ) { s += it.next(); if (it.hasNext()) { s += ", "; } } return s + "]"; } else { String s = "("; for (Term t : listArgs) { s += t; s += " :: "; } return s + cur + ")"; } } } private static Constructor makeNil(ConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate(sym, args, () -> new Nil(sym, args)); } private static Constructor makeCons(ConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate(sym, args, () -> new Cons(sym, args)); } private static Constructor makeIntConst(ConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { String repr = ((Primitive) args[0]).getVal().toString(); if (repr.charAt(0) == '-') { shim.print("(- " + repr.substring(1, repr.length()) + ")"); } else { shim.print(repr); } } }); } private static Constructor lookupOrCreateParameterizedConstructor( ParameterizedConstructorSymbol sym, Term[] args) { Function makeSolverOp = op -> memo.lookupOrCreate(sym, args, () -> new SolverOperation(sym, args, op)); BuiltInConstructorSymbolBase preSym = sym.getBase(); switch (preSym) { case SMT_EQ: return makeSolverOp.apply("="); case SMT_LET: return SolverLet.make(sym, args); case ARRAY_DEFAULT: return makeSolverOp.apply("default"); case ARRAY_SELECT: return makeSolverOp.apply("select"); case BV_SGE: return makeSolverOp.apply("bvsge"); case BV_SGT: return makeSolverOp.apply("bvsgt"); case BV_SLE: return makeSolverOp.apply("bvsle"); case BV_SLT: return makeSolverOp.apply("bvslt"); case BV_UGE: return makeSolverOp.apply("bvuge"); case BV_UGT: return makeSolverOp.apply("bvugt"); case BV_ULE: return makeSolverOp.apply("bvule"); case BV_ULT: return makeSolverOp.apply("bvult"); case FP_EQ: return makeSolverOp.apply("fp.eq"); case FP_GE: return makeSolverOp.apply("fp.geq"); case FP_GT: return makeSolverOp.apply("fp.gt"); case FP_IS_NAN: return makeSolverOp.apply("fp.isNaN"); case FP_LE: return makeSolverOp.apply("fp.leq"); case FP_LT: return makeSolverOp.apply("fp.lt"); case BV_TO_BV_SIGNED: return makeBvToBvSigned(sym, args); case BV_TO_BV_UNSIGNED: return makeBvToBvUnsigned(sym, args); case FP_TO_FP: return makeFpToFp(sym, args); case BV_TO_FP: return makeBvToFp(sym, args); case FP_TO_SBV: return makeFpToBv(sym, args, true); case FP_TO_UBV: return makeFpToBv(sym, args, false); case BV_CONST: return makeBvConst(sym, args); case BV_BIG_CONST: return makeBvBigConst(sym, args); case INT_TO_BV: return makeIntToBv(sym, args); case BV_TO_INT: return makeSolverOp.apply("bv2int"); case BV_CONCAT: return makeSolverOp.apply("concat"); case BV_EXTRACT: return makeBvExtract(sym, args); case FP_BIG_CONST: case FP_CONST: return makeConstant(sym, args); case SMT_PAT: case SMT_WRAP_VAR: return memo.lookupOrCreate(sym, args, () -> new VanillaConstructor(sym, args)); case SMT_VAR: return memo.lookupOrCreate(sym, args, () -> new SolverVariable(sym, args)); } throw new AssertionError("impossible"); } private static int nat(Param param) { return ((TypeIndex) param.getType()).getIndex(); } private static int nat(ParameterizedConstructorSymbol sym, int idx) { return nat(sym.getArgs().get(idx)); } private static Constructor makeBvConst(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { I32 arg = (I32) args[0]; int width = nat(sym.getArgs().get(0)); String s = Integer.toBinaryString(arg.getVal()); shim.print(formatBitString(s, width)); } }); } private static Constructor makeBvBigConst(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { I64 arg = (I64) args[0]; int width = nat(sym.getArgs().get(0)); String s = Long.toBinaryString(arg.getVal()); shim.print(formatBitString(s, width)); } }); } private static String formatBitString(String bitString, int width) { int len = bitString.length(); if (width > len) { String pad = ""; for (int w = len; w < width; w++) { pad += "0"; } bitString = pad + bitString; } else if (width < len) { bitString = bitString.substring(len - width, len); } return "#b" + bitString; } private static Constructor makeIntToBv(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { shim.print("((_ int2bv "); int width = nat(sym.getArgs().get(0)); shim.print(width + ") "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }); } private static Constructor makeBvExtract(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { shim.print("((_ extract "); shim.print(args[2] + " " + args[1] + ") "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }); } private static Constructor makeConstant(ConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { ((SmtLibTerm) args[0]).toSmtLib(shim); } }); } private static Constructor makeBvToBvSigned(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { int idx1 = nat(sym, 0); int idx2 = nat(sym, 1); SmtLibTerm t = (SmtLibTerm) args[0]; if (idx1 < idx2) { shim.print("("); shim.print("(_ sign_extend " + (idx2 - idx1) + ") "); t.toSmtLib(shim); shim.print(")"); } else if (idx1 == idx2) { t.toSmtLib(shim); } else { shim.print("("); shim.print("(_ extract " + (idx2 - 1) + " 0) "); t.toSmtLib(shim); shim.print(")"); } } }); } private static Constructor makeBvToBvUnsigned(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { int idx1 = nat(sym, 0); int idx2 = nat(sym, 1); SmtLibTerm t = (SmtLibTerm) args[0]; if (idx1 < idx2) { shim.print("("); shim.print("(_ zero_extend " + (idx2 - idx1) + ") "); t.toSmtLib(shim); shim.print(")"); } else if (idx1 == idx2) { t.toSmtLib(shim); } else { shim.print("("); shim.print("(_ extract " + (idx2 - 1) + " 0) "); t.toSmtLib(shim); shim.print(")"); } } }); } private static Constructor makeBvToFp(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { int exponent = nat(sym, 1); int significand = nat(sym, 2); shim.print("((_ to_fp " + exponent + " " + significand + ") RNE "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }); } private static Constructor makeFpToFp(ParameterizedConstructorSymbol sym, Term[] args) { return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { int exponent = nat(sym, 2); int significand = nat(sym, 3); shim.print("((_ to_fp " + exponent + " " + significand + ") RNE "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }); } private static Constructor makeFpToBv( ParameterizedConstructorSymbol sym, Term[] args, boolean signed) { String s = signed ? "fp.to_sbv" : "fp.to_ubv"; return memo.lookupOrCreate( sym, args, () -> new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { int width = nat(sym, 2); shim.print("((_ " + s + " " + width + ") RNE "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }); } private abstract static class AbstractConstructor extends AbstractTerm implements Constructor { protected final S sym; protected final Term[] args; protected final boolean isGround; protected final boolean containsFunctionCall; protected AbstractConstructor(S sym, Term[] args) { assert noneNull(args) : sym; this.sym = sym; this.args = args; boolean g = true; boolean f = false; for (Term t : args) { g &= t.isGround(); f |= t.containsUnevaluatedTerm(); } isGround = g; containsFunctionCall = f; } private boolean noneNull(Term[] ts) { for (Term t : ts) { if (t == null) { return false; } } return true; } @Override public boolean isGround() { return isGround; } @Override public boolean containsUnevaluatedTerm() { return containsFunctionCall; } @Override public S getSymbol() { return sym; } @Override public Term[] getArgs() { return args; } @Override public String toString() { return FunctorUtil.toString(sym, args); } @Override public Term copyWithNewArgs(Term[] args) { return make(sym, args); } @Override public SmtLibTerm substSolverTerms(PMap subst) { if (subst.containsKey(this)) { return subst.get(this); } Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = ((SmtLibTerm) args[i]).substSolverTerms(subst); } return (SmtLibTerm) copyWithNewArgs(newArgs); } @Override public Set freeVars() { Set vars = new HashSet<>(); for (Term t : args) { vars.addAll(((SmtLibTerm) t).freeVars()); } return vars; } } public static class VanillaConstructor extends AbstractConstructor { private VanillaConstructor(ConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } } public static class Tuple extends AbstractConstructor { private Tuple(TupleSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } @Override public String toString() { StringBuilder sb = new StringBuilder("("); for (int i = 0; i < args.length; ++i) { sb.append(args[i]); if (i < args.length - 1) { sb.append(", "); } } sb.append(")"); return sb.toString(); } } public static class Record extends AbstractConstructor { private Record(RecordSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } @Override public String toString() { StringBuilder sb = new StringBuilder("{ "); int i = 0; for (FunctionSymbol label : ((RecordSymbol) sym).getLabels()) { sb.append(label); sb.append("="); sb.append(args[i]); sb.append("; "); ++i; } sb.append("}"); return sb.toString(); } } public static class SolverVariable extends AbstractConstructor { private static final AtomicInteger cnt = new AtomicInteger(); private final int solverVarId = cnt.getAndIncrement(); public SolverVariable(ParameterizedConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { shim.print(this); } public int getSolverVarId() { return solverVarId; } @Override public String toString() { if (Configuration.simplifyFormulaVars) { return "#x" + getSolverVarId(); } Type ty = ((FunctorType) sym.getCompileTimeType()).getRetType(); ty = ((AlgebraicDataType) ty).getTypeArgs().get(0); return "#{" + args[0] + "}[" + ty + "]"; } @Override public Set freeVars() { return Collections.singleton(this); } } private static Constructor makeConstructorTester(ConstructorSymbol sym, Term[] args) { assert sym.toString().matches("#is_.*"); String s = "|is-" + sym.toString().substring(4) + "|"; return new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { shim.print("("); shim.print(s); shim.print(" "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }; } private static Constructor makeConstructorGetter(ConstructorSymbol sym, Term[] args) { String s = "|" + sym.toString() + "|"; return new AbstractConstructor(sym, args) { @Override public void toSmtLib(SmtLibShim shim) { shim.print("("); shim.print(s); shim.print(" "); ((SmtLibTerm) args[0]).toSmtLib(shim); shim.print(")"); } }; } public static class SolverUninterpretedFunction extends AbstractConstructor { protected SolverUninterpretedFunction(ConstructorSymbol sym, Term[] args) { super(sym, args); } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, shim); } } public static class SolverOperation extends AbstractConstructor { private final String op; protected SolverOperation(ConstructorSymbol sym, Term[] args, String op) { super(sym, args); this.op = op; } @Override public void toSmtLib(SmtLibShim shim) { Constructors.toSmtLib(this, op, shim); } private String getSyntax() { if (sym instanceof ParameterizedConstructorSymbol) { if (((ParameterizedConstructorSymbol) sym) .getBase() .equals(BuiltInConstructorSymbolBase.SMT_EQ)) { return "#="; } } if (sym instanceof BuiltInConstructorSymbol) { switch ((BuiltInConstructorSymbol) sym) { case SMT_AND: return "/\\"; case SMT_IMP: return "==>"; case SMT_NOT: return "~"; case SMT_OR: return "\\/"; default: return null; } } return null; } @Override public String toString() { String syntax = getSyntax(); if (syntax == null) { return super.toString(); } if (args.length == 1) { return "(" + syntax + " " + args[0] + ")"; } if (args.length == 2) { return "(" + args[0] + " " + syntax + " " + args[1] + ")"; } throw new AssertionError("impossible"); } } private static class SolverIte extends SolverOperation { protected SolverIte(ConstructorSymbol sym, Term[] args) { super(sym, args, "ite"); } @Override public String toString() { return "(#if " + args[0] + " then " + args[1] + " else " + args[2] + ")"; } } private static void toSmtLib(Constructor c, String repr, SmtLibShim shim) { ConstructorSymbol sym = c.getSymbol(); if (sym.getArity() > 0) { shim.print("("); } String typeAnnotation = shim.getTypeAnnotation(sym); if (typeAnnotation != null) { shim.print("(as "); shim.print(repr); shim.print(" "); shim.print(typeAnnotation); shim.print(")"); } else { shim.print(repr); } for (Term t : c.getArgs()) { SmtLibTerm tt = (SmtLibTerm) t; shim.print(" "); tt.toSmtLib(shim); } if (sym.getArity() > 0) { shim.print(")"); } } private static void toSmtLib(Constructor c, SmtLibShim shim) { toSmtLib(c, c.getSymbol().toString(), shim); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Expr.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; public interface Expr extends Term { O accept(ExprVisitor visitor, I in); O accept(ExprVisitorExn visitor, I in) throws E; @Override default O accept(TermVisitor visitor, I in) { return visitor.visit(this, in); } @Override default O accept(TermVisitorExn visitor, I in) throws E { return visitor.visit(this, in); } @Override default boolean containsUnevaluatedTerm() { return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Exprs.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; public final class Exprs { private Exprs() { throw new AssertionError("impossible"); } public static interface ExprVisitor { O visit(MatchExpr matchExpr, I in); O visit(FunctionCall funcCall, I in); O visit(LetFunExpr funcDefs, I in); O visit(Fold fold, I in); } public static interface ExprVisitorExn { O visit(MatchExpr matchExpr, I in) throws E; O visit(FunctionCall funcCall, I in) throws E; O visit(LetFunExpr funcDefs, I in) throws E; O visit(Fold fold, I in) throws E; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/FP32.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.PMap; public class FP32 extends AbstractTerm implements Primitive, SmtLibTerm { private static final Map memo = new ConcurrentHashMap<>(); private final float val; private FP32(float val) { this.val = val; } public static FP32 make(float val) { return Util.lookupOrCreate(memo, val, () -> new FP32(val)); } @Override public Float getVal() { return val; } @Override public String toString() { if (Float.isNaN(val)) { return "fp32_nan"; } if (val == Float.POSITIVE_INFINITY) { return "fp32_pos_infinity"; } if (val == Float.NEGATIVE_INFINITY) { return "fp32_neg_infinity"; } return Float.toString(val) + "F"; } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public void toSmtLib(SmtLibShim shim) { if (Float.isNaN(val)) { shim.print("(_ NaN 8 24)"); } else if (val == Float.POSITIVE_INFINITY) { shim.print("(_ +oo 8 24)"); } else if (val == Float.NEGATIVE_INFINITY) { shim.print("(_ -oo 8 24)"); } else { shim.print("((_ to_fp 8 24) RNE " + val + ")"); } } @Override public Type getType() { return BuiltInTypes.fp32; } @Override public Set freeVars() { return Collections.emptySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/FP64.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.PMap; public class FP64 extends AbstractTerm implements Primitive, SmtLibTerm { private static final Map memo = new ConcurrentHashMap<>(); private final double val; private FP64(double val) { this.val = val; } public static FP64 make(double val) { return Util.lookupOrCreate(memo, val, () -> new FP64(val)); } @Override public Double getVal() { return val; } @Override public String toString() { if (Double.isNaN(val)) { return "fp64_nan"; } if (val == Double.POSITIVE_INFINITY) { return "fp64_pos_infinity"; } if (val == Double.NEGATIVE_INFINITY) { return "fp64_neg_infinity"; } return Double.toString(val); } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public void toSmtLib(SmtLibShim shim) { if (Double.isNaN(val)) { shim.print("(_ NaN 11 53)"); } else if (val == Double.POSITIVE_INFINITY) { shim.print("(_ +oo 11 53)"); } else if (val == Double.NEGATIVE_INFINITY) { shim.print("(_ -oo 11 53)"); } else { shim.print("((_ to_fp 11 53) RNE " + val + ")"); } } @Override public Type getType() { return BuiltInTypes.fp64; } @Override public Set freeVars() { return Collections.emptySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Fold.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Arrays; import java.util.Map; import java.util.Set; public class Fold implements Expr { private final FunctionSymbol f; private final Term[] args; private final FunctionCallFactory funCalls; private Boolean isGround; private Fold(FunctionSymbol f, Term[] args, FunctionCallFactory funCalls) { this.f = f; this.args = args; this.funCalls = funCalls; if (f.getArity() != args.length) { throw new IllegalArgumentException( "Arity mismatch when defining fold over " + f + " (it needs " + f.getArity() + " arguments, but got the arguments " + Arrays.toString(args) + ")."); } } public static Fold mk(FunctionSymbol f, Term[] args, FunctionCallFactory funCalls) { return new Fold(f, args, funCalls); } public Term[] getArgs() { return args; } public FunctionSymbol getFunction() { return f; } public FunctionCall getShamCall() { return funCalls.make(f, args); } @Override public boolean isGround() { if (isGround != null) { return isGround; } for (Term arg : args) { if (!arg.isGround()) { return (isGround = false); } } return (isGround = true); } @Override public Term applySubstitution(Substitution s) { Term[] newArgs = new Term[args.length]; int i = 0; for (Term arg : args) { newArgs[i] = arg.applySubstitution(s); i++; } return new Fold(f, newArgs, funCalls); } @Override public Term normalize(Substitution s) throws EvaluationException { Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].normalize(s); } Term acc = newArgs[0]; Constructor list = (Constructor) newArgs[1]; FunctionDef def = funCalls.getDefManager().lookup(f); while (list.getSymbol().equals(BuiltInConstructorSymbol.CONS)) { Term[] consCell = list.getArgs(); Term head = consCell[0]; list = (Constructor) consCell[1]; newArgs[0] = acc; newArgs[1] = head; acc = def.evaluate(newArgs); } return acc; } @Override public void varSet(Set acc) { for (Term arg : args) { arg.varSet(acc); } } @Override public void updateVarCounts(Map counts) { for (Term arg : args) { arg.updateVarCounts(counts); } } @Override public int getId() { throw new UnsupportedOperationException(); } @Override public O accept(ExprVisitor visitor, I in) { return visitor.visit(this, in); } @Override public O accept(ExprVisitorExn visitor, I in) throws E { return visitor.visit(this, in); } @Override public String toString() { String s = "fold[" + f + "]("; for (int i = 0; i < args.length; ++i) { s += args[i]; if (i < args.length - 1) { s += ", "; } } return s + ")"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/FunctionCallFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.FunctorUtil; import edu.harvard.seas.pl.formulog.util.FunctorUtil.Memoizer; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; public final class FunctionCallFactory { private final FunctionDefManager defManager; private final Memoizer memo = new Memoizer<>(); private static final AtomicInteger cnt = new AtomicInteger(); private static final boolean debug = System.getProperty("callTrace") != null; private static final int memoizeThreshold = Configuration.memoizeThreshold(); private final Map, Term>> callMemo = new ConcurrentHashMap<>(); public FunctionCallFactory(FunctionDefManager defManager) { this.defManager = defManager; } public FunctionCall make(FunctionSymbol sym, Term[] args) { if (sym.getArity() != args.length) { throw new IllegalArgumentException( "Symbol " + sym + " has arity " + sym.getArity() + " but args " + Arrays.toString(args) + " have length " + args.length); } return memo.lookupOrCreate(sym, args, () -> new FunctionCall(sym, args)); } public FunctionDefManager getDefManager() { return defManager; } public void clearMemoCache() { callMemo.clear(); } public class FunctionCall extends AbstractTerm implements Functor, Expr { private final FunctionSymbol sym; private final Term[] args; private final boolean isGround; private FunctionCall(FunctionSymbol sym, Term[] args) { this.sym = sym; this.args = args; boolean b = true; for (Term t : args) { b &= t.isGround(); } isGround = b; } @Override public FunctionSymbol getSymbol() { return sym; } @Override public Term[] getArgs() { return args; } @Override public FunctionCall copyWithNewArgs(Term[] newArgs) { return make(sym, newArgs); } @Override public boolean containsUnevaluatedTerm() { return true; } @Override public Term applySubstitution(Substitution s) { if (isGround) { return this; } Term[] args = getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].applySubstitution(s); } return make(sym, newArgs); } @Override public boolean isGround() { return isGround; } @Override public String toString() { return FunctorUtil.toString(sym, args); } @Override public Term normalize(Substitution s) throws EvaluationException { Integer id = null; Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].normalize(s); } if (debug) { id = cnt.getAndIncrement(); String msg = "BEGIN CALL #" + id + "\n"; msg += "Function: " + sym + "\n"; msg += "Arguments: " + Arrays.toString(newArgs) + "\n"; msg += "***\n"; System.err.println(msg); } Term r; try { if (memoizeThreshold > -1 && !hasSideEffects()) { r = computeWithMemoization(newArgs); } else { r = computeWithoutMemoization(newArgs); } } catch (Throwable e) { throw new EvaluationException(e); } if (debug) { String msg = "END CALL #" + id + "\n"; msg += "Function: " + sym + "\n"; msg += "Arguments: " + Arrays.toString(newArgs) + "\n"; msg += "Result: " + r + "\n"; msg += "***\n"; System.err.println(msg); } return r; } private boolean hasSideEffects() { // XXX This is rough, since it doesn't take into account the call // graph (i.e., A could call B which is side effecting, but this // would say that A is not). return sym.equals(BuiltInFunctionSymbol.PRINT); } private Term computeWithMemoization(Term[] newArgs) throws EvaluationException { Map, Term> m = Util.lookupOrCreate(callMemo, sym, () -> new ConcurrentHashMap<>()); List key = Arrays.asList(newArgs); Term r = m.get(key); if (r == null) { long start = System.nanoTime(); r = defManager.lookup(sym).evaluate(newArgs); long time = (System.nanoTime() - start) / 1000000; if (Configuration.recordFuncDiagnostics) { Configuration.recordFuncTime(sym, time); } if (time >= memoizeThreshold) { m.put(key, r); } } return r; } private Term computeWithoutMemoization(Term[] newArgs) throws EvaluationException { long start = 0; if (Configuration.recordFuncDiagnostics) { start = System.nanoTime(); } Term r = defManager.lookup(sym).evaluate(newArgs); if (Configuration.recordFuncDiagnostics) { long time = (System.nanoTime() - start) / 1000000; Configuration.recordFuncTime(sym, time); } return r; } public FunctionCallFactory getFactory() { return FunctionCallFactory.this; } @Override public O accept(ExprVisitor visitor, I in) { return visitor.visit(this, in); } @Override public O accept(ExprVisitorExn visitor, I in) throws E { return visitor.visit(this, in); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Functor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.symbols.Symbol; import java.util.Map; import java.util.Set; public interface Functor extends Term { S getSymbol(); Term[] getArgs(); Term copyWithNewArgs(Term[] args); @Override default void varSet(Set acc) { if (!isGround()) { for (Term arg : getArgs()) { arg.varSet(acc); } } } @Override default void updateVarCounts(Map counts) { for (Term arg : getArgs()) { arg.updateVarCounts(counts); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/I32.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.PMap; public class I32 extends AbstractTerm implements Primitive, SmtLibTerm { private static final Map memo = new ConcurrentHashMap<>(); private final int val; private I32(int val) { this.val = val; } public static I32 make(int val) { return Util.lookupOrCreate(memo, val, () -> new I32(val)); } @Override public Integer getVal() { return val; } @Override public String toString() { return Integer.toString(val); } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public void toSmtLib(SmtLibShim shim) { shim.print("#x" + String.format("%08x", val)); } @Override public Type getType() { return BuiltInTypes.i32; } @Override public Set freeVars() { return Collections.emptySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/I64.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.PMap; public class I64 extends AbstractTerm implements Primitive, SmtLibTerm { private static final Map memo = new ConcurrentHashMap<>(); private final long val; private I64(long val) { this.val = val; } public static I64 make(long val) { return Util.lookupOrCreate(memo, val, () -> new I64(val)); } @Override public Long getVal() { return val; } @Override public String toString() { return Long.toString(val) + "L"; } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public void toSmtLib(SmtLibShim shim) { shim.print("#x" + String.format("%016x", val)); } @Override public Type getType() { return BuiltInTypes.i64; } @Override public Set freeVars() { return Collections.emptySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/LetFunExpr.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class LetFunExpr implements Expr { private final Set defs; private final Term letBody; private LetFunExpr(Set defs, Term letBody) { Set freshDefs = new HashSet<>(); for (NestedFunctionDef def : defs) { freshDefs.add(def.freshen()); } this.defs = Collections.unmodifiableSet(freshDefs); this.letBody = letBody; } public static LetFunExpr make(Set defs, Term letBody) { return new LetFunExpr(defs, letBody); } public Set getDefs() { return defs; } public Term getLetBody() { return letBody; } @Override public boolean isGround() { for (NestedFunctionDef def : defs) { if (!def.getBody().isGround()) { return false; } } return letBody.isGround(); } @Override public Term applySubstitution(Substitution s) { Set newDefs = new HashSet<>(); for (NestedFunctionDef def : defs) { newDefs.add(def.applySubstitution(s)); } return make(newDefs, letBody.applySubstitution(s)); } @Override public Term normalize(Substitution s) throws EvaluationException { throw new UnsupportedOperationException(); } @Override public void varSet(Set acc) { for (NestedFunctionDef def : defs) { Set vars = def.getBody().varSet(); vars.removeAll(def.getParams()); acc.addAll(vars); } } @Override public void updateVarCounts(Map counts) { throw new UnsupportedOperationException("LetFunExpr.updateVarCounts() unimplemented"); } @Override public int getId() { throw new UnsupportedOperationException(); } @Override public O accept(ExprVisitor visitor, I in) { return visitor.visit(this, in); } @Override public O accept(ExprVisitorExn visitor, I in) throws E { return visitor.visit(this, in); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((defs == null) ? 0 : defs.hashCode()); result = prime * result + ((letBody == null) ? 0 : letBody.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LetFunExpr other = (LetFunExpr) obj; if (defs == null) { if (other.defs != null) return false; } else if (!defs.equals(other.defs)) return false; if (letBody == null) { if (other.letBody != null) return false; } else if (!letBody.equals(other.letBody)) return false; return true; } @Override public String toString() { String s = "let fun "; for (Iterator it = defs.iterator(); it.hasNext(); ) { s += it.next(); if (it.hasNext()) { s += "\nand "; } } return s + " in\n" + letBody; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Literal.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; public interface Literal { Term[] getArgs(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/MatchClause.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; public class MatchClause { private final Term lhs; private final Term rhs; public static MatchClause make(Term lhs, Term rhs) { if (!checkForRepeatVariables(lhs)) { throw new IllegalArgumentException("Cannot repeat variables in patterns: " + lhs); } Substitution s = new SimpleSubstitution(); for (Var x : lhs.varSet()) { if (!x.isUnderscore()) { s.put(x, Var.fresh(x.toString())); } } return new MatchClause(lhs.applySubstitution(s), rhs.applySubstitution(s)); } private static boolean checkForRepeatVariables(Term pat) { return pat.accept(varsDistinct, new HashSet<>()); } private static TermVisitor, Boolean> varsDistinct = new TermVisitor, Boolean>() { @Override public Boolean visit(Var x, Set in) { if (x.isUnderscore()) { return true; } return in.add(x.getName()); } @Override public Boolean visit(Constructor c, Set in) { boolean ok = true; for (Term t : c.getArgs()) { ok &= t.accept(this, in); } return ok; } @Override public Boolean visit(Primitive p, Set in) { return true; } @Override public Boolean visit(Expr e, Set in) { throw new AssertionError("Shouldn't be expressions in patterns"); } }; MatchClause(Term lhs, Term rhs) { this.lhs = lhs; this.rhs = rhs; } public boolean tryMatch(Term t, Substitution s) { Deque> stack = new ArrayDeque<>(); stack.add(new Pair<>(lhs, t)); while (!stack.isEmpty()) { Pair p = stack.remove(); Term pat = p.fst(); Term scrutinee = p.snd(); if (pat.equals(scrutinee)) { continue; } if (pat instanceof Var) { s.put((Var) pat, scrutinee); } else if (pat instanceof Primitive) { return false; } else { Constructor cpat = (Constructor) pat; Constructor cscrutinee = (Constructor) scrutinee; if (!cpat.getSymbol().equals(cscrutinee.getSymbol())) { return false; } Term[] args1 = cpat.getArgs(); Term[] args2 = cscrutinee.getArgs(); for (int i = 0; i < args1.length; ++i) { stack.add(new Pair<>(args1[i], args2[i])); } } } return true; } public Term getLhs() { return lhs; } public Term getRhs() { return rhs; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((lhs == null) ? 0 : lhs.hashCode()); result = prime * result + ((rhs == null) ? 0 : rhs.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MatchClause other = (MatchClause) obj; if (lhs == null) { if (other.lhs != null) return false; } else if (!lhs.equals(other.lhs)) return false; if (rhs == null) { if (other.rhs != null) return false; } else if (!rhs.equals(other.rhs)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/MatchExpr.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public class MatchExpr extends AbstractTerm implements Expr, Iterable { private final Term matchee; private final List match; private final boolean isGround; public static MatchExpr make(Term matchee, List match) { return new MatchExpr(matchee, match); } MatchExpr(Term matchee, List match) { this.matchee = matchee; this.match = match; boolean isGround = matchee.isGround(); if (isGround) { for (MatchClause cl : match) { Set vars = cl.getRhs().varSet(); Set patVars = cl.getLhs().varSet(); if (!patVars.containsAll(vars)) { isGround = false; break; } } } this.isGround = isGround; } public Term getMatchee() { return matchee; } public List getClauses() { return Collections.unmodifiableList(match); } @Override public O accept(ExprVisitorExn visitor, I in) throws E { return visitor.visit(this, in); } @Override public Term normalize(Substitution s) throws EvaluationException { Term e = matchee.normalize(s); for (MatchClause m : match) { if (m.tryMatch(e, s)) { return m.getRhs().normalize(s); } } if (e instanceof Constructor) { throw new EvaluationException( "No matching pattern for " + matchee + " which normalizes to a complex term with outermost constructor " + ((Constructor) e).getSymbol()); } else { throw new EvaluationException( "No matching pattern for " + matchee + " which normalizes to the term " + e); } // throw new EvaluationException("No matching pattern for " + e + " under // substitution " + s); } @Override public O accept(ExprVisitor visitor, I in) { return visitor.visit(this, in); } @Override public Term applySubstitution(Substitution s) { if (isGround) { return this; } Term newMatchee = matchee.applySubstitution(s); List clauses = new ArrayList<>(); for (MatchClause cl : match) { Substitution newS = new SimpleSubstitution(); Term pat = cl.getLhs(); Set patVars = pat.varSet(); for (Var x : s.iterateKeys()) { if (!patVars.contains(x)) { newS.put(x, s.get(x)); } } Term newRhs = cl.getRhs().applySubstitution(newS); clauses.add(new MatchClause(pat, newRhs)); } return make(newMatchee, clauses); } @Override public String toString() { String s = "match " + matchee.toString() + " with"; for (MatchClause cl : match) { s += "\n\t| " + cl.getLhs() + " => " + cl.getRhs(); } s += "\nend"; return s; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((match == null) ? 0 : match.hashCode()); result = prime * result + ((matchee == null) ? 0 : matchee.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MatchExpr other = (MatchExpr) obj; if (match == null) { if (other.match != null) return false; } else if (!match.equals(other.match)) return false; if (matchee == null) { if (other.matchee != null) return false; } else if (!matchee.equals(other.matchee)) return false; return true; } @Override public boolean isGround() { return isGround; } @Override public void varSet(Set acc) { if (!isGround) { matchee.varSet(acc); for (MatchClause cl : match) { Set vars = cl.getRhs().varSet(); vars.removeAll(cl.getLhs().varSet()); acc.addAll(vars); } } } @Override public Iterator iterator() { return match.iterator(); } @Override public void updateVarCounts(Map counts) { matchee.updateVarCounts(counts); for (MatchClause match : this) { Map counts2 = new HashMap<>(); match.getRhs().updateVarCounts(counts2); Set boundVars = match.getLhs().varSet(); for (Map.Entry e : counts2.entrySet()) { Var x = e.getKey(); if (!boundVars.contains(x)) { int n = Util.lookupOrCreate(counts, x, () -> 0); counts.put(x, n + e.getValue()); } } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Model.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class Model extends AbstractTerm implements Primitive> { private static final Map, Model> memo = new ConcurrentHashMap<>(); private final Map m; private Model(Map m) { this.m = m; } public static Model make(Map m) { return Util.lookupOrCreate(memo, m, () -> new Model(m)); } @Override public Map getVal() { return m; } @Override public Type getType() { return BuiltInTypes.model; } @Override public String toString() { String s = "["; for (Iterator> it = m.entrySet().iterator(); it.hasNext(); ) { Map.Entry e = it.next(); s += e.getKey() + " -> " + e.getValue(); if (it.hasNext()) { s += ", "; } } return s + "]"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/NestedFunctionDef.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class NestedFunctionDef { private final FunctionSymbol sym; private final List params; private final Term body; private NestedFunctionDef(FunctionSymbol sym, List params, Term body) { this.sym = sym; this.params = Collections.unmodifiableList(params); this.body = body; if (sym.getArity() != params.size()) { throw new IllegalArgumentException( "Mismatch between arity and number of given parameters in nested function definition: " + sym); } } public static NestedFunctionDef make(FunctionSymbol sym, List params, Term body) { return new NestedFunctionDef(sym, params, body); } public NestedFunctionDef applySubstitution(Substitution s) { Substitution s2 = new SimpleSubstitution(); for (Var x : s.iterateKeys()) { if (!params.contains(x)) { s2.put(x, s.get(x)); } } return make(sym, params, body.applySubstitution(s2)); } public NestedFunctionDef freshen() { List newParams = new ArrayList<>(); Substitution s = new SimpleSubstitution(); for (Var param : params) { Var newParam = Var.fresh(param.toString()); newParams.add(newParam); s.put(param, newParam); } return make(sym, newParams, body.applySubstitution(s)); } public FunctionSymbol getSymbol() { return sym; } public List getParams() { return params; } public Term getBody() { return body; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((params == null) ? 0 : params.hashCode()); result = prime * result + ((body == null) ? 0 : body.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NestedFunctionDef other = (NestedFunctionDef) obj; if (params == null) { if (other.params != null) return false; } else if (!params.equals(other.params)) return false; if (body == null) { if (other.body != null) return false; } else if (!body.equals(other.body)) return false; return true; } @Override public String toString() { String s = "" + sym; if (!params.isEmpty()) { s += "("; for (int i = 0; i < params.size(); ++i) { s += params.get(i); if (i < params.size() - 1) { s += ", "; } } s += ")"; } return s + " = " + body; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/OpaqueSet.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2021-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.HashPMap; import org.pcollections.IntTreePMap; import org.pcollections.MapPSet; import org.pcollections.PSet; public class OpaqueSet extends AbstractTerm implements Primitive> { private final PSet s; private OpaqueSet(PSet s) { this.s = s; } private static final OpaqueSet empty; private static final Map, OpaqueSet> memo = new ConcurrentHashMap<>(); static { PSet mt = MapPSet.from(HashPMap.empty(IntTreePMap.empty())); empty = make(mt); } public static OpaqueSet empty() { return empty; } public static OpaqueSet singleton(Term t) { return empty.plus(t); } public static OpaqueSet fromCollection(Collection c) { PSet s = MapPSet.from(HashPMap.empty(IntTreePMap.empty())); for (Term t : c) { s = s.plus(t); } return make(s); } private static OpaqueSet make(PSet s) { return Util.lookupOrCreate(memo, s, () -> new OpaqueSet(s)); } public Collection getCollection() { return s; } public boolean member(Term t) { return s.contains(t); } public OpaqueSet plus(Term t) { return make(s.plus(t)); } public OpaqueSet minus(Term t) { return make(s.minus(t)); } public OpaqueSet union(OpaqueSet other) { return make(s.plusAll(other.s)); } public OpaqueSet diff(OpaqueSet other) { return make(s.minusAll(other.s)); } public int size() { return s.size(); } public boolean isEmpty() { return s.isEmpty(); } public Pair choose() { if (isEmpty()) { return null; } Term t = s.iterator().next(); return new Pair<>(t, make(s.minus(t))); } public boolean containsAll(OpaqueSet other) { return s.containsAll(other.s); } @Override public Set getVal() { return s; } @Override public Type getType() { return BuiltInTypes.opaqueSet(BuiltInTypes.a); } @Override public String toString() { String str = s.toString(); return "{" + str.substring(1, str.length() - 1) + "}"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Primitive.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Map; import java.util.Set; public interface Primitive extends Term { T getVal(); @Override default O accept(TermVisitor v, I in) { return v.visit(this, in); } @Override default O accept(TermVisitorExn v, I in) throws E { return v.visit(this, in); } @Override public default boolean containsUnevaluatedTerm() { return false; } @Override public default Term applySubstitution(Substitution s) { return this; } @Override public default Term normalize(Substitution s) { return this; } @Override public default boolean isGround() { return true; } Type getType(); @Override public default void varSet(Set acc) { // do nothing } @Override public default void updateVarCounts(Map counts) { // do nothing } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Program.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import java.util.Set; public interface Program> { Set getFunctionSymbols(); Set getFactSymbols(); Set getRuleSymbols(); FunctionDef getDef(FunctionSymbol sym); Set getFacts(RelationSymbol sym); Set getRules(RelationSymbol sym); SymbolManager getSymbolManager(); boolean hasQuery(); Q getQuery(); FunctionCallFactory getFunctionCallFactory(); Set getUninterpretedFunctionSymbols(); Set getTypeSymbols(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Rule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import java.util.Collections; import java.util.HashMap; import java.util.Map; public interface Rule extends Iterable { H getHead(); int getBodySize(); B getBody(int idx); default Map countVariables() { Map m = new HashMap<>(); for (Term arg : getHead().getArgs()) { arg.updateVarCounts(m); } for (Literal l : this) { for (Term arg : l.getArgs()) { arg.updateVarCounts(m); } } return Collections.unmodifiableMap(m); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/SmtLibTerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import java.util.Set; import org.pcollections.PMap; public interface SmtLibTerm extends Term { void toSmtLib(SmtLibShim shim); SmtLibTerm substSolverTerms(PMap subst); Set freeVars(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/StringTerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.pcollections.PMap; public class StringTerm extends AbstractTerm implements Primitive, SmtLibTerm { private static final Map memo = new ConcurrentHashMap<>(); private final String val; private StringTerm(String val) { this.val = val; } public static StringTerm make(String val) { return Util.lookupOrCreate(memo, val, () -> new StringTerm(val)); } @Override public String getVal() { return val; } @Override public String toString() { return "\"" + val + "\""; } @Override public SmtLibTerm substSolverTerms(PMap subst) { return this; } @Override public void toSmtLib(SmtLibShim shim) { String s = val.replace("\"", "\"\""); shim.print("\"" + s + "\""); } @Override public Type getType() { return BuiltInTypes.string; } @Override public Set freeVars() { return Collections.emptySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Term.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.HashSet; import java.util.Map; import java.util.Set; public interface Term { O accept(TermVisitor v, I in); O accept(TermVisitorExn v, I in) throws E; boolean isGround(); boolean containsUnevaluatedTerm(); Term applySubstitution(Substitution s); Term normalize(Substitution s) throws EvaluationException; void varSet(Set acc); default Set varSet() { Set vars = new HashSet<>(); varSet(vars); return vars; } void updateVarCounts(Map counts); int getId(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Terms.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.ExceptionalFunction; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; public final class Terms { private Terms() { throw new AssertionError(); } private static final Term[] EMPTY_TERMS_ARR = new Term[0]; public static Term[] emptyArray() { return EMPTY_TERMS_ARR; } public static Term[] singletonArray(Term t) { return new Term[] {t}; } public static Term[] map(Term[] ts, Function f) { Term[] ys = new Term[ts.length]; for (int i = 0; i < ts.length; ++i) { ys[i] = f.apply(ts[i]); } return ys; } public static Term[] mapExn(Term[] ts, ExceptionalFunction f) throws E { Term[] ys = new Term[ts.length]; for (int i = 0; i < ts.length; ++i) { ys[i] = f.apply(ts[i]); } return ys; } public static Term[] normalize(Term[] ts, Substitution s) throws EvaluationException { Term[] newTs = new Term[ts.length]; for (int i = 0; i < ts.length; ++i) { newTs[i] = ts[i].normalize(s); } return newTs; } public static boolean isGround(Term t, Set boundVars) { return boundVars.containsAll(t.varSet()); } public static Set getNonBindingVarInstances(Term t) { Set vars = new HashSet<>(); t.accept( new TermVisitor() { @Override public Void visit(Var t, Void in) { return null; } @Override public Void visit(Constructor t, Void in) { for (Term arg : t.getArgs()) { arg.accept(this, in); } return null; } @Override public Void visit(Primitive prim, Void in) { return null; } @Override public Void visit(Expr expr, Void in) { vars.addAll(expr.varSet()); return null; } }, null); return vars; } public static Set getBindingVarInstances(Term t) { Set vars = new HashSet<>(); t.accept( new TermVisitor() { @Override public Void visit(Var t, Void in) { vars.add(t); return null; } @Override public Void visit(Constructor t, Void in) { for (Term arg : t.getArgs()) { arg.accept(this, in); } return null; } @Override public Void visit(Primitive prim, Void in) { return null; } @Override public Void visit(Expr expr, Void in) { return null; } }, null); return vars; } public static interface TermVisitor { O visit(Var t, I in); O visit(Constructor c, I in); O visit(Primitive p, I in); O visit(Expr e, I in); } public static interface TermVisitorExn { O visit(Var x, I in) throws E; O visit(Constructor c, I in) throws E; O visit(Primitive p, I in) throws E; O visit(Expr e, I in) throws E; } public static final Term minTerm = new DummyTerm(Integer.MIN_VALUE); public static final Term maxTerm = new DummyTerm(Integer.MAX_VALUE); private static class DummyTerm implements Term { private final int id; public DummyTerm(int id) { this.id = id; } @Override public O accept(TermVisitor v, I in) { throw new UnsupportedOperationException(); } @Override public O accept(TermVisitorExn v, I in) throws E { throw new UnsupportedOperationException(); } @Override public boolean isGround() { return true; } @Override public boolean containsUnevaluatedTerm() { return false; } @Override public Term applySubstitution(Substitution s) { throw new UnsupportedOperationException(); } @Override public Term normalize(Substitution s) throws EvaluationException { throw new UnsupportedOperationException(); } @Override public void varSet(Set acc) { throw new UnsupportedOperationException(); } @Override public int getId() { return id; } @Override public void updateVarCounts(Map counts) { throw new UnsupportedOperationException(); } @Override public String toString() { return "DummyTerm(" + id + ")"; } } public static Term makeDummyTerm(int id) { return new DummyTerm(id); } private static final AtomicInteger idCnt = new AtomicInteger(0); public static int nextId() { return idCnt.incrementAndGet(); } @SuppressWarnings("unchecked") public static List termToTermList(Term t) { List xs = new ArrayList<>(); Constructor c = (Constructor) t; while (c.getSymbol() == BuiltInConstructorSymbol.CONS) { Term[] args = c.getArgs(); xs.add((T) args[0]); c = (Constructor) args[1]; } assert c.getSymbol() == BuiltInConstructorSymbol.NIL; return xs; } public static Term termListToTerm(List l) { Term acc = Constructors.nil(); for (ListIterator it = l.listIterator(l.size()); it.hasPrevious(); ) { acc = Constructors.cons(it.previous(), acc); } return acc; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/UnificationPredicate.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Arrays; public class UnificationPredicate implements ComplexLiteral { private final Term[] args; private final boolean negated; public static UnificationPredicate make(Term lhs, Term rhs, boolean negated) { return new UnificationPredicate(new Term[] {lhs, rhs}, negated); } private UnificationPredicate(Term[] args, boolean negated) { this.args = args; this.negated = negated; } @Override public Term[] getArgs() { return args; } public Term getLhs() { return args[0]; } public Term getRhs() { return args[1]; } @Override public UnificationPredicate applySubstitution(Substitution subst) { Term newLhs = getLhs().applySubstitution(subst); Term newRhs = getRhs().applySubstitution(subst); return make(newLhs, newRhs, negated); } @Override public boolean isNegated() { return negated; } @Override public String toString() { return getLhs() + (negated ? " != " : " = ") + getRhs(); } @Override public O accept(ComplexLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(ComplexLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(args); result = prime * result + (negated ? 1231 : 1237); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; UnificationPredicate other = (UnificationPredicate) obj; if (!Arrays.equals(args, other.args)) return false; if (negated != other.negated) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/UserPredicate.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Arrays; public class UserPredicate implements ComplexLiteral { private final RelationSymbol symbol; private final Term[] args; private final boolean negated; public static UserPredicate make(RelationSymbol symbol, Term[] args, boolean negated) { return new UserPredicate(symbol, args, negated); } private UserPredicate(RelationSymbol symbol, Term[] args, boolean negated) { this.symbol = symbol; this.args = args; this.negated = negated; } public RelationSymbol getSymbol() { return symbol; } @Override public Term[] getArgs() { return args; } @Override public boolean isNegated() { return negated; } @Override public UserPredicate applySubstitution(Substitution subst) { Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].applySubstitution(subst); } return make(symbol, newArgs, negated); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(args); result = prime * result + (negated ? 1231 : 1237); result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; UserPredicate other = (UserPredicate) obj; if (!Arrays.equals(args, other.args)) return false; if (negated != other.negated) return false; if (symbol == null) { if (other.symbol != null) return false; } else if (!symbol.equals(other.symbol)) return false; return true; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (negated) { sb.append("!"); } sb.append(symbol); int arity = args.length; if (arity > 0) { sb.append('('); for (int i = 0; i < arity; ++i) { sb.append(args[i]); if (i < arity - 1) { sb.append(", "); } } sb.append(')'); } return sb.toString(); } @Override public O accept(ComplexLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(ComplexLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/ast/Var.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.ast; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public class Var extends AbstractTerm { static final AtomicInteger cnt = new AtomicInteger(); private final String name; protected Var(String name) { this.name = name; } public static Var fresh(String name) { return new Var(name); } public static Var fresh() { return new Var("$" + cnt.getAndIncrement()); } public boolean isUnderscore() { return name.equals("_"); } public String getName() { return name; } @Override public String toString() { return name; } @Override public O accept(TermVisitor v, I in) { return v.visit(this, in); } @Override public O accept(TermVisitorExn v, I in) throws E { return v.visit(this, in); } @Override public boolean isGround() { return false; } @Override public boolean containsUnevaluatedTerm() { return false; } @Override public Term applySubstitution(Substitution s) { if (s.containsKey(this)) { return s.get(this); } return this; } @Override public Term normalize(Substitution s) throws EvaluationException { assert s.containsKey(this); return s.get(this); } @Override public void varSet(Set acc) { acc.add(this); } @Override public void updateVarCounts(Map counts) { int n = Util.lookupOrCreate(counts, this, () -> 0); counts.put(this, n + 1); } private static final Var hole = new Var("??"); public static Var makeHole() { return hole; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/CodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.magic.MagicSetTransformer; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParamKind; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.util.Util; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; public class CodeGen { private final BasicProgram prog; private final File outDir; public CodeGen(BasicProgram prog, File outDir) { this.prog = prog; this.outDir = outDir; } public void go() throws IOException, URISyntaxException, CodeGenException { CodeGenContext ctx = new CodeGenContext(prog); // Make sure that we generate a symbol for SMT variables (assumed in the C++ // runtime code) ParameterizedConstructorSymbol sym = GlobalSymbolManager.getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_VAR) .copyWithNewArgs( new Param(BuiltInTypes.bool, ParamKind.ANY_TYPE), new Param(BuiltInTypes.bool, ParamKind.PRE_SMT_TYPE)); String repr = ctx.lookupRepr(sym); assert repr.equals("Symbol::smt_var__bool__bool") : repr; copy("CMakeLists.txt"); copySrc("time.hpp"); copySrc("ConcurrentHashMap.hpp"); new FuncsHpp(ctx).gen(outDir); new MainCpp(ctx).gen(outDir); new SouffleCodeGen(ctx).gen(outDir); copySrc("smt_solver.h"); copySrc("smt_solver.cpp"); copySrc("smt_shim.h"); new SmtShimCpp(ctx).gen(outDir); copySrc("smt_parser.hpp"); new SmtParserCpp(ctx).gen(outDir); copySrc("Type.hpp"); new TypeCpp(ctx).gen(outDir); copySrc("Term.hpp"); new TermCpp(ctx).gen(outDir); copySrc("Tuple.hpp"); copySrc("parser.hpp"); copySrc("parser.cpp"); new SymbolHpp(ctx).gen(outDir); new SymbolCpp(ctx).gen(outDir); copySrc("globals.h"); copySrc("set.cpp"); copySrc("set.hpp"); } private void copy(String name) throws IOException { var inFile = Path.of("codegen", name).toString(); try (InputStream is = getClass().getClassLoader().getResourceAsStream(inFile)) { assert is != null : name; Files.copy(is, outDir.toPath().resolve(name), StandardCopyOption.REPLACE_EXISTING); } } private void copySrc(String name) throws IOException { copy(Path.of("src", name).toString()); } public static void main(String[] args) throws Exception { if (args.length != 1) { throw new IllegalArgumentException("Expected a single argument (the Formulog source file)"); } main(new File(args[0]), new File("codegen")); } public static void main(File file, File outDir) throws Exception { Program prog; try (FileReader fr = new FileReader(file)) { prog = new Parser().parse(fr); } WellTypedProgram wtp = new TypeChecker(prog).typeCheck(); MagicSetTransformer mst = new MagicSetTransformer(wtp); BasicProgram magicProg = mst.transform(Configuration.useDemandTransformation, Configuration.restoreStratification); File srcDir = outDir.toPath().resolve("src").toFile(); srcDir.mkdirs(); Util.clean(srcDir, false); new CodeGen(magicProg, outDir).go(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/CodeGenContext.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SFunctorBody; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SIntListType; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRuleMode; import edu.harvard.seas.pl.formulog.symbols.*; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class CodeGenContext { private final Map ctorSymToRepr = new HashMap<>(); private final Map funcSymToRepr = new HashMap<>(); private final AtomicInteger id = new AtomicInteger(); private final Map functorBody = new ConcurrentHashMap<>(); private final Set souffleTypes = new HashSet<>(); private final Map> customRelations = new HashMap<>(); private final Map exprFunctorNames = new HashMap<>(); private final Map dtorFunctorNames = new HashMap<>(); private final Map reprToRelSym = new HashMap<>(); private final BasicProgram prog; public CodeGenContext(BasicProgram prog) { this.prog = prog; new Worker().go(); } public BasicProgram getProgram() { return prog; } public Set getConstructorSymbols() { return Collections.unmodifiableSet(ctorSymToRepr.keySet()); } public synchronized String lookupUnqualifiedRepr(ConstructorSymbol sym) { String repr = ctorSymToRepr.get(sym); if (repr == null) { repr = CodeGenUtil.mkName(sym); String repr2 = ctorSymToRepr.putIfAbsent(sym, repr); assert repr2 == null; } return repr; } public synchronized String lookupRepr(ConstructorSymbol sym) { return "Symbol::" + lookupUnqualifiedRepr(sym); } public synchronized String lookupRepr(FunctionSymbol sym) { String repr = funcSymToRepr.get(sym); assert repr != null : sym; return "funcs::" + repr; } public synchronized String lookupRepr(RelationSymbol sym) { var repr = sym.toString().replace(":", "__") + "_"; reprToRelSym.put(repr, sym); return repr; } public synchronized RelationSymbol lookupRel(String repr) { return reprToRelSym.get(repr); } public synchronized void register(FunctionSymbol sym, String repr) { String repr2 = funcSymToRepr.put(sym, repr); assert repr2 == null || repr2.equals(repr); } public synchronized void register(SIntListType type) { souffleTypes.add(type); } public synchronized Set getSouffleTypes() { return Collections.unmodifiableSet(souffleTypes); } public String newId(String prefix) { return prefix + id.getAndIncrement(); } public synchronized String lookupOrCreateFunctorName(Term t) { return Util.lookupOrCreate(exprFunctorNames, t, () -> "expr_" + id.getAndIncrement()); } public synchronized String lookupOrCreateFunctorName(ConstructorSymbol sym) { return Util.lookupOrCreate(dtorFunctorNames, sym, () -> "dtor_" + id.getAndIncrement()); } public void registerFunctorBody(String functor, SFunctorBody body) { functorBody.put(functor, body); } public Set> getFunctors() { Set> s = new HashSet<>(); for (Map.Entry e : functorBody.entrySet()) { s.add(new Pair<>(e.getKey(), e.getValue())); } return Collections.unmodifiableSet(s); } public Set>> getCustomRelations() { return customRelations.entrySet().stream() .map(e -> new Pair<>(e.getKey(), e.getValue())) .collect(Collectors.toSet()); } public void registerCustomRelation(String name, Integer arity, SRuleMode mode) { var p = new Pair<>(arity, mode); var other = customRelations.put(name, p); assert other == null || other.equals(p); } private class Worker { public Worker() {} public void go() { processTypes(prog.getTypeSymbols()); for (ConstructorSymbol sym : BuiltInConstructorSymbol.values()) { lookupRepr(sym); } } private void processTypes(Set typeSymbols) { for (TypeSymbol sym : typeSymbols) { if (!sym.isAlias()) { processType(AlgebraicDataType.makeWithFreshArgs(sym)); } } } private void processType(AlgebraicDataType type) { if (type.hasConstructors()) { for (ConstructorScheme cs : type.getConstructors()) { ConstructorSymbol sym = cs.getSymbol(); lookupRepr(sym); } } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/CodeGenException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; public class CodeGenException extends Exception { private static final long serialVersionUID = -3022070471185499421L; public CodeGenException(String message) { super(message); } public CodeGenException(Exception cause) { super(cause); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/CodeGenUtil.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppAccess; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppCast; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppConst; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppExpr; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppStmt; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppSubscript; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppUnop; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedSymbol; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.Iterator; public final class CodeGenUtil { private CodeGenUtil() { throw new AssertionError("impossible"); } public static void printIndent(PrintWriter out, int indent) { for (int i = 0; i < indent; ++i) { out.print(" "); } } public static void print(Iterable stmts, PrintWriter out, int indent) { for (CppStmt stmt : stmts) { stmt.println(out, indent); } } public static void printSeparated(Iterable exprs, String sep, PrintWriter out) { for (Iterator it = exprs.iterator(); it.hasNext(); ) { it.next().print(out); if (it.hasNext()) { out.print(sep); } } } public static CppExpr mkComplexTermLookup(CppExpr base, int offset) { CppExpr cast = CppCast.mkReinterpret("const ComplexTerm&", CppUnop.mkDeref(base)); CppExpr access = CppAccess.mk(cast, "val"); return CppSubscript.mk(access, CppConst.mkInt(offset)); } public static void copyOver(BufferedReader in, PrintWriter out, int stopAt) throws IOException { String line; while ((line = in.readLine()) != null && !line.equals("/* INSERT " + stopAt + " */")) { out.println(line); } } public static String mkName(Symbol sym) { String s; if (sym instanceof ParameterizedSymbol) { ParameterizedSymbol psym = (ParameterizedSymbol) sym; StringBuilder sb = new StringBuilder(psym.getBase().toString()); for (Param p : psym.getArgs()) { sb.append("__"); String ty = p.getType().toString().replaceAll("[^A-Za-z0-9_]+", "_"); sb.append(ty); } s = sb.toString(); } else { s = sym.toString().replaceAll("[^A-Za-z0-9_]+", "_"); } if (sym instanceof FunctionSymbol && !(sym instanceof BuiltInFunctionSymbol)) { s = "FLG_" + s; } return s; } public static String toString(CppStmt stmt, int indent) { StringWriter sw = new StringWriter(); stmt.println(new PrintWriter(sw), indent); return sw.toString(); } public static InputStream inputSrcFile(String file) { var cl = CodeGenUtil.class.getClassLoader(); var path = Path.of("codegen", "src", file).toString(); var is = cl.getResourceAsStream(path); assert is != null : path; return is; } public static File outputSrcFile(File topDir, String file) { return topDir.toPath().resolve("src").resolve(file).toFile(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/DeltaFirstQueryPlanner.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SAtom; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRule; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.validating.Stratum; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class DeltaFirstQueryPlanner implements QueryPlanner { private final CodeGenContext ctx; public DeltaFirstQueryPlanner(CodeGenContext ctx) { this.ctx = ctx; } @Override public List> genPlan(SRule r, Stratum stratum) { List> l = new ArrayList<>(); var p = findDeltas(r, stratum); var numAtoms = p.fst(); int i = 0; for (var deltaPos : p.snd()) { l.add(new Pair<>(i++, genPlanForDelta(deltaPos, numAtoms))); } return l; } private Pair> findDeltas(SRule r, Stratum stratum) { List l = new ArrayList<>(); var body = r.getBody(); var preds = stratum.getPredicateSyms(); int numAtoms = 0; for (int i = 0; i < body.size(); ++i) { var lit = body.get(i); if (lit instanceof SAtom) { numAtoms++; var pred = ((SAtom) lit).getSymbol(); var rel = ctx.lookupRel(pred); if (rel != null && preds.contains(rel)) { l.add(i + 1); } } } return new Pair<>(numAtoms, l); } private int[] genPlanForDelta(int delta, int numAtoms) { int[] arr = new int[numAtoms]; Arrays.setAll( arr, (i) -> { if (i == 0) { return delta; } else if (i < delta) { return i; } else { return i + 1; } }); return arr; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/FuncsHpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.functions.*; import edu.harvard.seas.pl.formulog.symbols.*; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.*; import java.util.stream.Collectors; public class FuncsHpp extends TemplateSrcFile { public FuncsHpp(CodeGenContext ctx) { super("funcs.hpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker pr = new Worker(out); CodeGenUtil.copyOver(br, out, 0); pr.makeDeclarations(); CodeGenUtil.copyOver(br, out, 1); pr.makeDefinitions(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final PrintWriter out; private final Set userDefinedFunctions = new HashSet<>(); private final Set predFunctions = new HashSet<>(); private final TermCodeGen tcg = new TermCodeGen(ctx); public Worker(PrintWriter out) { this.out = out; } private void makeDeclarations() { for (FunctionSymbol sym : ctx.getProgram().getFunctionSymbols()) { if (sym instanceof BuiltInFunctionSymbol) { registerBuiltInFunction((BuiltInFunctionSymbol) sym); } else if (sym instanceof PredicateFunctionSymbol) { predFunctions.add((PredicateFunctionSymbol) sym); declareFunction(sym); } else { FunctionDef def = ctx.getProgram().getDef(sym); if (def instanceof UserFunctionDef) { userDefinedFunctions.add(sym); declareFunction(sym); } else if (def instanceof RecordAccessor) { ctx.register(sym, "__access<" + ((RecordAccessor) def).getIndex() + ">"); } else { throw new AssertionError("Unexpected function def: " + sym + " " + def.getClass()); } } } } private void declareFunction(FunctionSymbol sym) { String name = CodeGenUtil.mkName(sym); ctx.register(sym, name); out.println(); out.print("term_ptr " + name + "("); int n = sym.getArity(); for (int i = 0; i < n; ++i) { out.print("term_ptr"); if (i < n - 1) { out.print(", "); } } out.println(");"); } private void registerBuiltInFunction(BuiltInFunctionSymbol sym) { switch (sym) { case BEQ: ctx.register(sym, "beq"); break; case BNEQ: ctx.register(sym, "bneq"); break; case BNOT: ctx.register(sym, "bnot"); break; case FP32_ADD: ctx.register(sym, "__add"); break; case FP32_DIV: ctx.register(sym, "__div"); break; case FP32_EQ: ctx.register(sym, "__eq"); break; case FP32_GE: ctx.register(sym, "__ge"); break; case FP32_GT: ctx.register(sym, "__gt"); break; case FP32_LE: ctx.register(sym, "__le"); break; case FP32_LT: ctx.register(sym, "__lt"); break; case FP32_MUL: ctx.register(sym, "__mul"); break; case FP32_NEG: ctx.register(sym, "__neg"); break; case FP32_REM: ctx.register(sym, "__rem"); break; case FP32_SUB: ctx.register(sym, "__sub"); break; case FP64_ADD: ctx.register(sym, "__add"); break; case FP64_DIV: ctx.register(sym, "__div"); break; case FP64_EQ: ctx.register(sym, "__eq"); break; case FP64_GE: ctx.register(sym, "__ge"); break; case FP64_GT: ctx.register(sym, "__gt"); break; case FP64_LE: ctx.register(sym, "__le"); break; case FP64_LT: ctx.register(sym, "__lt"); break; case FP64_MUL: ctx.register(sym, "__mul"); break; case FP64_NEG: ctx.register(sym, "__neg"); break; case FP64_REM: ctx.register(sym, "__rem"); break; case FP64_SUB: ctx.register(sym, "__sub"); break; case I32_ADD: ctx.register(sym, "__add"); break; case I32_AND: ctx.register(sym, "__bitwise_and"); break; case I32_SDIV: ctx.register(sym, "__div"); break; case I32_GE: ctx.register(sym, "__ge"); break; case I32_GT: ctx.register(sym, "__gt"); break; case I32_LE: ctx.register(sym, "__le"); break; case I32_LT: ctx.register(sym, "__lt"); break; case I32_MUL: ctx.register(sym, "__mul"); break; case I32_NEG: ctx.register(sym, "__neg"); break; case I32_OR: ctx.register(sym, "__bitwise_or"); break; case I32_SREM: ctx.register(sym, "__rem"); break; case I32_SCMP: ctx.register(sym, "__cmp"); break; case I32_SUB: ctx.register(sym, "__sub"); break; case I32_UCMP: ctx.register(sym, "__cmp"); break; case I32_XOR: ctx.register(sym, "__bitwise_xor"); break; case I32_SHL: ctx.register(sym, "__shl"); break; case I32_ASHR: ctx.register(sym, "__ashr"); break; case I32_LSHR: ctx.register(sym, "__lshr"); break; case I32_UREM: ctx.register(sym, "__urem"); break; case I32_UDIV: ctx.register(sym, "__udiv"); break; case I64_ADD: ctx.register(sym, "__add"); break; case I64_AND: ctx.register(sym, "__bitwise_and"); break; case I64_SDIV: ctx.register(sym, "__div"); break; case I64_GE: ctx.register(sym, "__ge"); break; case I64_GT: ctx.register(sym, "__gt"); break; case I64_LE: ctx.register(sym, "__le"); break; case I64_LT: ctx.register(sym, "__lt"); break; case I64_MUL: ctx.register(sym, "__mul"); break; case I64_NEG: ctx.register(sym, "__neg"); break; case I64_OR: ctx.register(sym, "__bitwise_or"); break; case I64_SREM: ctx.register(sym, "__rem"); break; case I64_SCMP: ctx.register(sym, "__cmp"); break; case I64_SUB: ctx.register(sym, "__sub"); break; case I64_UCMP: ctx.register(sym, "__cmp"); break; case I64_XOR: ctx.register(sym, "__bitwise_xor"); break; case I64_SHL: ctx.register(sym, "__shl"); break; case I64_ASHR: ctx.register(sym, "__ashr"); break; case I64_LSHR: ctx.register(sym, "__lshr"); break; case I64_UREM: ctx.register(sym, "__urem"); break; case I64_UDIV: ctx.register(sym, "__udiv"); break; case IS_SAT: ctx.register(sym, "is_sat"); break; case IS_SAT_OPT: ctx.register(sym, "is_sat_opt"); break; case IS_VALID: ctx.register(sym, "is_valid"); break; case GET_MODEL: ctx.register(sym, "get_model"); break; case QUERY_MODEL: ctx.register(sym, "query_model"); break; case PRINT: ctx.register(sym, "print"); break; case STRING_CONCAT: ctx.register(sym, "string_concat"); break; case STRING_CMP: ctx.register(sym, "__cmp"); break; case STRING_MATCHES: ctx.register(sym, "string_matches"); break; case STRING_STARTS_WITH: ctx.register(sym, "string_starts_with"); break; case TO_STRING: ctx.register(sym, "to_string"); break; case fp32ToFp64: ctx.register(sym, "__conv"); break; case fp32ToI32: ctx.register(sym, "__conv"); break; case fp32ToI64: ctx.register(sym, "__conv"); break; case fp64ToFp32: ctx.register(sym, "__conv"); break; case fp64ToI32: ctx.register(sym, "__conv"); break; case fp64ToI64: ctx.register(sym, "__conv"); break; case i32ToFp32: ctx.register(sym, "__conv"); break; case i32ToFp64: ctx.register(sym, "__conv"); break; case i32ToI64: ctx.register(sym, "__conv"); break; case i64ToFp32: ctx.register(sym, "__conv"); break; case i64ToFp64: ctx.register(sym, "__conv"); break; case i64ToI32: ctx.register(sym, "__conv"); break; case CHAR_AT: ctx.register(sym, "char_at"); break; case STRING_LENGTH: ctx.register(sym, "string_length"); break; case STRING_TO_LIST: ctx.register(sym, "string_to_list"); break; case LIST_TO_STRING: ctx.register(sym, "list_to_string"); break; case SUBSTRING: ctx.register(sym, "substring"); break; case stringToI32: ctx.register(sym, "string_to_i32"); break; case stringToI64: ctx.register(sym, "string_to_i64"); break; case OPAQUE_SET_EMPTY: ctx.register(sym, "opaque_set_empty"); break; case OPAQUE_SET_PLUS: ctx.register(sym, "opaque_set_plus"); break; case OPAQUE_SET_MINUS: ctx.register(sym, "opaque_set_minus"); break; case OPAQUE_SET_UNION: ctx.register(sym, "opaque_set_union"); break; case OPAQUE_SET_DIFF: ctx.register(sym, "opaque_set_diff"); break; case OPAQUE_SET_CHOOSE: ctx.register(sym, "opaque_set_choose"); break; case OPAQUE_SET_SIZE: ctx.register(sym, "opaque_set_size"); break; case OPAQUE_SET_MEMBER: ctx.register(sym, "opaque_set_member"); break; case OPAQUE_SET_SINGLETON: ctx.register(sym, "opaque_set_singleton"); break; case OPAQUE_SET_SUBSET: ctx.register(sym, "opaque_set_subset"); break; case OPAQUE_SET_FROM_LIST: ctx.register(sym, "opaque_set_from_list"); break; case IS_SET_SAT: ctx.register(sym, "is_set_sat"); break; default: throw new AssertionError("unhandled built-in function symbol: " + sym); } } private void makeDefinitions() { var sorted = userDefinedFunctions.stream() .sorted(Comparator.comparing(Object::toString)) .collect(Collectors.toList()); for (FunctionSymbol sym : sorted) { makeDefinitionForUserDefinedFunc(sym); } for (PredicateFunctionSymbol funcSym : predFunctions) { makeDefinitionForPredFunc(funcSym); } } private void makeDefinitionForUserDefinedFunc(FunctionSymbol sym) { UserFunctionDef def = (UserFunctionDef) ctx.getProgram().getDef(sym); out.println(); out.print("term_ptr " + ctx.lookupRepr(sym) + "("); Map env = new HashMap<>(); Iterator params = def.getParams().iterator(); int n = sym.getArity(); List cppParams = new ArrayList<>(); for (int i = 0; i < n; ++i) { String id = ctx.newId("x"); CppVar var = CppVar.mk(id); env.put(params.next(), var); out.print("term_ptr "); var.print(out); if (i < n - 1) { out.print(", "); } cppParams.add(var.toString()); } out.println(") {"); if (n == 0) { out.println(" static std::atomic memo{nullptr};"); out.println(" if (memo) { return memo; }"); } else { if (n == 1) { out.println(" typedef term_ptr Key;"); out.println(" Key key = " + cppParams.get(0) + ";"); } else { out.println(" typedef std::array Key;"); out.println(" Key key = {" + String.join(", ", cppParams) + "};"); } out.println(" static ConcurrentHashMap> memo;"); out.println(" auto it = memo.find(key);"); out.println(" if (it != memo.end()) { return it->second; }"); } Pair p = tcg.gen(def.getBody(), env); p.fst().println(out, 1); String retVar = ctx.newId("ret"); CppDecl.mk(retVar, p.snd()).println(out, 1); if (n == 0) { out.println(" memo = " + retVar + ";"); } else { out.println(" memo.emplace(key, " + retVar + ");"); } CppReturn.mk(CppVar.mk(retVar)).println(out, 1); out.println("}"); } private void makeDefinitionForPredFunc(PredicateFunctionSymbol funcSym) { out.println(); out.print("term_ptr " + ctx.lookupRepr(funcSym) + "("); RelationSymbol predSym = funcSym.getPredicateSymbol(); int n = predSym.getArity(); BindingType[] bindings = funcSym.getBindings(); int j = 0; int nbound = numBound(bindings); List free = new ArrayList<>(); boolean hasIgnored = false; CppVar[] args = new CppVar[bindings.length]; for (int i = 0; i < n; ++i) { if (bindings[i].isBound()) { String id = ctx.newId("x"); CppVar var = CppVar.mk(id); args[i] = var; out.print("term_ptr "); var.print(out); if (j < nbound - 1) { out.print(", "); } j++; } else if (bindings[i].isFree()) { free.add(i); } else if (bindings[i].isIgnored()) { hasIgnored = true; } } if (hasIgnored) { System.err.println( "WARNING: relation query " + funcSym + " has ignored attribute; this might lead to slow performance"); } out.println(") "); Pair p; if (free.isEmpty()) { p = genRelationContains(predSym, args, hasIgnored); } else if (free.size() == 1) { p = genRelationAggMono(predSym, args, free.get(0)); } else { p = genRelationAggPoly(predSym, args, free); } CppStmt ret = CppReturn.mk(p.snd()); CppBlock.mk(CppSeq.mk(p.fst(), ret)).println(out, 0); } private CppExpr mkArgsVec(CppVar[] args) { var cppArgs = Arrays.stream(args) .map(x -> x == null ? CppNullptr.INSTANCE : x) .collect(Collectors.toList()); return CppVectorLiteral.mk(cppArgs); } private Pair genRelationAggMono( RelationSymbol predSym, CppVar[] args, int pos) { CppExpr name = CppConst.mkString(ctx.lookupRepr(predSym)); CppExpr vec = mkArgsVec(args); CppExpr call = CppFuncCall.mk("_relation_agg_mono", name, vec, CppConst.mkInt(pos)); return new Pair<>(CppSeq.skip(), call); } private Pair genRelationAggPoly( RelationSymbol predSym, CppVar[] args, List free) { CppExpr name = CppConst.mkString(ctx.lookupRepr(predSym)); CppExpr vec = mkArgsVec(args); List projection = new ArrayList<>(); int pos = 0; for (int i = 0; i < predSym.getArity(); ++i) { if (i == free.get(pos)) { projection.add(CppConst.mkTrue()); pos++; } else { projection.add(CppConst.mkFalse()); } } assert pos == free.size(); ConstructorSymbol tupleSym = GlobalSymbolManager.lookupTupleSymbol(free.size()); String funcName = "_relation_agg_poly<" + ctx.lookupRepr(tupleSym) + ">"; CppExpr call = CppFuncCall.mk(funcName, name, vec, CppVectorLiteral.mk(projection)); return new Pair<>(CppSeq.skip(), call); } private Pair genRelationContains( RelationSymbol predSym, CppVar[] args, boolean hasIgnored) { CppExpr name = CppConst.mkString(ctx.lookupRepr(predSym)); CppExpr vec = mkArgsVec(args); String func; if (hasIgnored) { func = "_relation_contains"; } else { func = "_relation_contains_complete"; } CppExpr call = CppFuncCall.mk(func, name, vec); return new Pair<>(CppSeq.skip(), call); } private int numBound(BindingType[] bindings) { int n = 0; for (BindingType binding : bindings) { if (binding.isBound()) { n++; } } return n; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/FunctorCodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SDestructorBody; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SExprBody; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SFunctorBody; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.*; import java.util.*; public class FunctorCodeGen { private final CodeGenContext ctx; private static final String headerName = "functors.h"; private static final String sourceName = "functors.cpp"; public FunctorCodeGen(CodeGenContext ctx) { this.ctx = ctx; } public void emitFunctors(File directory) throws CodeGenException { emitHeader(directory); emitSource(directory); } private void emitHeader(File directory) throws CodeGenException { try (InputStream is = CodeGenUtil.inputSrcFile(headerName)) { assert is != null; File outHeader = CodeGenUtil.outputSrcFile(directory, headerName); try (InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); PrintWriter out = new PrintWriter(outHeader)) { CodeGenUtil.copyOver(br, out, 0); for (Pair p : ctx.getFunctors()) { emitSignature(p.fst(), p.snd(), out); out.println(";"); } CodeGenUtil.copyOver(br, out, -1); } } catch (IOException e) { throw new CodeGenException(e); } } private Map emitSignature(String functor, SFunctorBody body, PrintWriter out) { out.print("souffle::RamDomain "); out.print(functor); out.print("("); if (body.isStateful()) { out.print("souffle::SymbolTable *st, souffle::RecordTable *rt"); if (body.getArity() > 0) { out.print(", "); } } List flgArgs = body.getArgs(); Map m = new LinkedHashMap<>(); for (int i = 0; i < body.getArity(); ++i) { out.print("souffle::RamDomain "); String arg = "arg" + i; m.put(flgArgs.get(i), CppVar.mk(arg)); out.print(arg); if (i < body.getArity() - 1) { out.print(", "); } } out.print(")"); return m; } private void emitSource(File directory) throws CodeGenException { try (InputStream is = CodeGenUtil.inputSrcFile(sourceName)) { assert is != null; File outSource = CodeGenUtil.outputSrcFile(directory, sourceName); try (InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); PrintWriter out = new PrintWriter(outSource)) { CodeGenUtil.copyOver(br, out, 0); new SourceWorker(out).emitFunctors(); CodeGenUtil.copyOver(br, out, -1); } } catch (IOException e) { throw new CodeGenException(e); } } private class SourceWorker { private final PrintWriter out; public SourceWorker(PrintWriter out_) { out = out_; } public void emitFunctors() { for (Pair p : ctx.getFunctors()) { emitFunctor(p.fst(), p.snd()); out.println(); } } private void emitFunctor(String functor, SFunctorBody body) { Map params = emitSignature(functor, body, out); out.print(" "); Pair> p1 = unpackParams(params); Pair p2; if (body instanceof SExprBody) { p2 = genTermBody(((SExprBody) body).getBody(), p1.snd()); } else { assert body instanceof SDestructorBody; p2 = genDtorBody((SDestructorBody) body, p1.snd()); } CppStmt ret = CppReturn.mk(p2.snd()); CppStmt block = CppBlock.mk(CppSeq.mk(p1.fst(), p2.fst(), ret)); block.println(out, 0); } private Pair> unpackParams(Map params) { List stmts = new ArrayList<>(); Map env = new HashMap<>(); for (Map.Entry e : params.entrySet()) { String x = ctx.newId("x"); CppExpr call = TermCodeGen.genUnintizeTerm(e.getValue()); CppStmt stmt = CppDecl.mk(x, call); stmts.add(stmt); env.put(e.getKey(), CppVar.mk(x)); } return new Pair<>(CppSeq.mk(stmts), env); } private Pair genTermBody(Term t, Map env) { TermCodeGen tcg = new TermCodeGen(ctx); Pair p = tcg.gen(t, env); CppExpr pack = TermCodeGen.genIntizeTerm(p.snd()); return new Pair<>(p.fst(), pack); } private Pair genDtorBody(SDestructorBody body, Map env) { Pair p = new TermCodeGen(ctx).gen(body.getScrutinee(), env); String func = "dtor<" + ctx.lookupRepr(body.getSymbol()) + ">"; CppExpr call = CppFuncCall.mk(func, p.snd()); return new Pair<>(p.fst(), call); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/MainCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.smt.PushPopSolver; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.*; public class MainCpp extends TemplateSrcFile { public MainCpp(CodeGenContext ctx) { super("main.cpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException, CodeGenException { Worker pr = new Worker(out); CodeGenUtil.copyOver(br, out, 0); pr.loadExternalEdbs(); CodeGenUtil.copyOver(br, out, 1); pr.loadStaticFacts(); CodeGenUtil.copyOver(br, out, 2); pr.printIdbsToDisk(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final PrintWriter out; public Worker(PrintWriter out) { this.out = out; } public void loadExternalEdbs() { for (RelationSymbol sym : ctx.getProgram().getFactSymbols()) { if (sym.isDisk()) { loadExternalEdbs(sym); } } } public void loadExternalEdbs(RelationSymbol sym) { String func = "loadEdbs"; CppExpr file = CppConst.mkString(sym + ".tsv"); CppExpr repr = CppConst.mkString(ctx.lookupRepr(sym)); CppExpr rel = CppMethodCall.mkThruPtr(CppVar.mk("globals::program"), "getRelation", repr); CppExpr call = CppFuncCall.mk(func, CppVar.mk("dir"), file, rel); call.toStmt().println(out, 1); } public void loadStaticFacts() throws CodeGenException { var prog = ctx.getProgram(); prog.getFunctionCallFactory().getDefManager().loadBuiltInFunctions(new PushPopSolver()); for (RelationSymbol sym : prog.getFactSymbols()) { for (Term[] tup : prog.getFacts(sym)) { loadStaticFact(sym, tup); } } } public void loadStaticFact(RelationSymbol sym, Term[] tup) throws CodeGenException { List args = new ArrayList<>(); List stmts = new ArrayList<>(); TermCodeGen tcg = new TermCodeGen(ctx); for (Term t : tup) { try { t = t.normalize(new SimpleSubstitution()); } catch (EvaluationException e) { throw new CodeGenException("Could not normalize term occurring in fact: " + t); } Pair p = tcg.gen(t, Collections.emptyMap()); stmts.add(p.fst()); args.add(p.snd()); } CppExpr rel = CppConst.mkString(ctx.lookupRepr(sym)); stmts.add(CppFuncCall.mk("loadFact", rel, CppVectorLiteral.mk(args)).toStmt()); CppSeq.mk(stmts).println(out, 1); } public void printIdbsToDisk() { for (RelationSymbol sym : ctx.getProgram().getRuleSymbols()) { if (sym.isDisk()) { out.println(" saveToDisk(\"" + ctx.lookupRepr(sym) + "\");"); } } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/MatchCodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.*; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.BaseSymbolicTerm; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.CtorEdge; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.DerivedSymbolicTerm; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.Edge; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.EdgeVisitor; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.InternalNode; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.Leaf; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.Node; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.NodeVisitor; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.PrimEdge; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.SymbolicTerm; import edu.harvard.seas.pl.formulog.codegen.PatternMatchTree.VarEdge; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.pcollections.HashPMap; import org.pcollections.HashTreePMap; public class MatchCodeGen { private final CodeGenContext ctx; private final TermCodeGen tcg; public MatchCodeGen(CodeGenContext ctx) { this.ctx = ctx; tcg = new TermCodeGen(ctx); } /** * Generate C++ code computing a match expression. * * @param match The match expression * @param env The current variable environment * @return A pair of the C++ code for the match expression and an expression representing the * result of the match */ public Pair gen(MatchExpr match, Map env) { return new Worker(new HashMap<>(env)).go(match); } /** * This class actually does all the work of generating the C++ code. It has a few "globals" that * are available throughout the pattern-matching computation: a variable {@link #res res} to store * the result of the pattern matching computation, and a label {@link #end end} to jump to after * the pattern matching computation is complete. * *

It essentially generates a bunch of nested if statements; the innermost code jumps to {@link * #end end} after assigning to {@link #res res}. Backtracking is implemented by falling through * to the next case. */ private class Worker { private final Map env; private final List acc = new ArrayList<>(); /** A variable to store the result of the pattern match in. */ private final String res = ctx.newId("res"); /** A label to jump to once the match expression has been computed. */ private final String end = ctx.newId("end"); public Worker(Map env) { this.env = env; } /** * Generate the C++ code for a match expression. * * @param match The match expression * @return A pair of the code and an expression holding the result of the match computation */ public Pair go(MatchExpr match) { var p = optimizeIfExpr(match); if (p != null) { return p; } acc.add(CppCtor.mk("term_ptr", res)); p = tcg.gen(match.getMatchee(), env); acc.add(p.fst()); String scrutineeVar = ctx.newId("scrutinee"); acc.add(CppDecl.mk(scrutineeVar, p.snd())); CppExpr scrutinee = CppVar.mk(scrutineeVar); List> clauses = preprocess(match.getClauses()); PatternMatchTree tree = new PatternMatchTree(clauses); acc.add(processTree(scrutinee, tree)); /* Generate some code that dies if no pattern has been matched. */ acc.add( new CppStmt() { @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print("cerr << \"No matching case for term: \" << "); CppUnop.mkDeref(scrutinee).print(out); out.println(" << endl;"); CodeGenUtil.printIndent(out, indent); out.println("cerr << "); out.println("R\"_(" + match + ")_\" << endl;"); CppFuncCall.mk("abort").toStmt().println(out, indent); } }); acc.add(CppLabel.mk(end)); return new Pair<>(CppSeq.mk(acc), CppVar.mk(res)); } private Pair optimizeIfExpr(MatchExpr match) { var scrutinee = match.getMatchee(); if (!(scrutinee instanceof FunctionCallFactory.FunctionCall)) { return null; } var call = (FunctionCallFactory.FunctionCall) scrutinee; var sym = call.getSymbol(); if (sym != BuiltInFunctionSymbol.BEQ && sym != BuiltInFunctionSymbol.BNEQ) { return null; } var clauses = match.getClauses(); if (clauses.size() != 2) { return null; } if (clauses.get(0).getLhs() != BoolTerm.mkTrue() || clauses.get(1).getLhs() != BoolTerm.mkFalse()) { return null; } var args = call.getArgs(); return optimizeIfExpr( sym == BuiltInFunctionSymbol.BEQ, args[0], args[1], clauses.get(0).getRhs(), clauses.get(1).getRhs()); } private Pair optimizeIfExpr( boolean equals, Term t1, Term t2, Term ifTrue, Term ifFalse) { List stmts = new ArrayList<>(); var p1 = tcg.gen(t1, env); var p2 = tcg.gen(t2, env); stmts.add(p1.fst()); stmts.add(p2.fst()); String res = ctx.newId("res"); stmts.add(CppCtor.mk("term_ptr", res)); CppExpr cond; if (equals) { cond = CppBinop.mkEq(p1.snd(), p2.snd()); } else { cond = CppBinop.mkNotEq(p1.snd(), p2.snd()); } p1 = tcg.gen(ifTrue, env); p2 = tcg.gen(ifFalse, env); CppExpr resVar = CppVar.mk(res); CppStmt trueBranch = CppSeq.mk(p1.fst(), CppBinop.mkAssign(resVar, p1.snd()).toStmt()); CppStmt falseBranch = CppSeq.mk(p2.fst(), CppBinop.mkAssign(resVar, p2.snd()).toStmt()); stmts.add(CppIf.mk(cond, trueBranch, falseBranch)); return new Pair<>(CppSeq.mk(stmts), resVar); } /** * Preprocess a list of match clauses, i.e., (pattern => expression) pairs. * * @param clauses The match clauses * @return The updated (pattern => expression) pairs */ private List> preprocess(List clauses) { List> l = new ArrayList<>(); Substitution s = new SimpleSubstitution(); Set vars = new HashSet<>(); /* * Rename variables in patterns, to ensure that patterns do not share variables * with each other. */ for (MatchClause clause : clauses) { Pair> p = renameVars(clause.getLhs(), s); Term rhs = clause.getRhs().applySubstitution(s); l.add(new Pair<>(p.fst(), rhs)); vars.addAll(p.snd()); } /* * Declare all the variables used in patterns. */ for (Var x : vars) { String id = ctx.newId("y"); CppVar cppX = CppVar.mk(id); env.put(x, cppX); acc.add(CppCtor.mk("term_ptr", id)); } return l; } /** * Rename the variables in a term. * * @param t The term * @param s A substitution to update with bindings from old variables to new ones * @return A pair of the new term and the set of variables in that term */ private Pair> renameVars(Term t, Substitution s) { Set seen = new HashSet<>(); Set newVars = new HashSet<>(); Term newT = t.accept( new TermVisitor() { @Override public Term visit(Var old, Void in) { assert seen.add(old) : "Cannot handle patterns with multiple uses of the same variable: " + t; Var renamed = Var.fresh(); newVars.add(renamed); s.put(old, renamed); return renamed; } @Override public Term visit(Constructor c, Void in) { Term[] args = c.getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].accept(this, in); } return c.copyWithNewArgs(newArgs); } @Override public Term visit(Primitive p, Void in) { return p; } @Override public Term visit(Expr e, Void in) { throw new AssertionError("impossible"); } }, null); return new Pair<>(newT, newVars); } /** * Given a C++ expression representing the scrutinee of a match expression and a * pattern-matching tree encoding the match expression's logic, generate C++ code implementing * the match expression. * * @param scrutinee The scrutinee * @param tree The pattern-matching tree * @return The generated C++ code */ private CppStmt processTree(CppExpr scrutinee, PatternMatchTree tree) { return new TreeProcessor(scrutinee, tree).go(); } /** This class is used to turn a pattern-matching tree into C++ code. */ private class TreeProcessor { private final CppExpr scrutinee; private final PatternMatchTree tree; public TreeProcessor(CppExpr scrutinee, PatternMatchTree tree) { this.scrutinee = scrutinee; this.tree = tree; } /** * Generate the C++ code encoding the pattern-matching logic of this object's pattern-matching * tree. * * @return The generated C++ code */ public CppStmt go() { return go(tree.getRoot(), HashTreePMap.empty()); } /** * Generate the C++ code corresponding to a node in the pattern matching tree. * * @param node The node * @return */ private CppStmt go(Node node, HashPMap symMap) { return node.accept( new NodeVisitor() { @Override public CppStmt visit(InternalNode node, Void in) { /* * You're at an internal node that is associated with a symbolic term (derived * from the scrutinee). Generate a C++ expression for the symbolic term, and * then generate code for checking that term against each outgoing edge of that * node. */ List stmts = new ArrayList<>(); SymbolicTerm symTerm = node.getSymbolicTerm(); /* Generate the C++ expression for the symbolic term. */ CppExpr expr; if (symTerm == BaseSymbolicTerm.INSTANCE) { expr = scrutinee; } else { DerivedSymbolicTerm dst = (DerivedSymbolicTerm) symTerm; CppExpr base = symMap.get(dst.getBase()); assert base != null; String id = ctx.newId("s"); stmts.add(CppDecl.mk(id, CodeGenUtil.mkComplexTermLookup(base, dst.getIndex()))); expr = CppVar.mk(id); } assert !(symMap.containsKey(symTerm)); var newMap = symMap.plus(symTerm, expr); /* Handle the outgoing edges. */ for (Pair, Node> p : tree.getOutgoingEdges(node)) { stmts.add(go(expr, p.fst(), p.snd(), newMap)); } return CppSeq.mk(stmts); } @Override public CppStmt visit(Leaf node, Void in) { /* * You've reached a leaf, so you've successfully matched the scrutinee against a * pattern. Evaluate the expression on the right-hand side of that pattern, * assign it to the result variable, and jump to the end label. */ Pair p = tcg.gen(node.getTerm(), env); CppStmt assign = CppBinop.mkAssign(CppVar.mk(res), p.snd()).toStmt(); CppStmt jump = CppGoto.mk(end); return CppSeq.mk(p.fst(), assign, jump); } }, null); } /** * Given a C++ expression representing the current sub-scrutinee, generate C++ code * implementing the pattern-matching logic encoded by the given edge and the rest of the tree * it leads to. * * @param expr The sub-scrutinee * @param edge The edge encoding the next step of pattern-matching logic * @param dest The destination of the edge, which leads to subsequent pattern-matching logic * @return The generated C++ code */ private CppStmt go( CppExpr expr, Edge edge, Node dest, HashPMap symMap) { return edge.accept( new EdgeVisitor() { @Override public CppStmt visit(VarEdge e, Void in) { CppExpr x = env.get(e.getLabel()); assert x instanceof CppVar; // Here, the enclosing `CppBlock` is necessary so that possible variable // initializations aren't in the outside scope, due to limitations of `goto` return CppBlock.mk( CppSeq.mk(CppBinop.mkAssign(x, expr).toStmt(), go(dest, symMap))); } @Override public CppStmt visit(PrimEdge e, Void in) { Pair p = tcg.gen(e.getLabel(), env); // Primitive edge should have no statements; otherwise, this could pollute the // enclosing scope and cause a compilation error, since `goto` can't jump over // variable initializations in C++ assert CodeGenUtil.toString(p.fst(), 0).isEmpty(); CppExpr rhs = p.snd(); CppExpr guard = CppBinop.mkEq(expr, rhs); CppStmt body = go(dest, symMap); return CppIf.mk(guard, body); } @Override public CppStmt visit(CtorEdge e, Void in) { CppExpr symbol = CppVar.mk(ctx.lookupRepr(e.getLabel())); CppExpr guard = CppBinop.mkEq(CppAccess.mkThruPtr(expr, "sym"), symbol); CppStmt body = go(dest, symMap); return CppIf.mk(guard, body); } }, null); } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/NopQueryPlanner.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRule; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.validating.Stratum; import java.util.List; public class NopQueryPlanner implements QueryPlanner { @Override public List> genPlan(SRule r, Stratum stratum) { return null; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/PatternMatchTree.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public class PatternMatchTree { private final Map, Node>>> m = new HashMap<>(); private final Node root; /** * Construct a pattern match tree from a list of (pattern => expression) pairs * * @param clauses The (pattern => expression) pairs */ public PatternMatchTree(List> clauses) { PatternMatchingComputation k = PatternMatchingComputation.mk(clauses); root = build(k); } /** * Return the root of the tree. * * @return The root */ public Node getRoot() { return root; } /** * Get a map from representing the outgoing edges of the given node. The map's iterator order * matches the priority of the edges (high to low). * * @param node The node * @return Map with the outgoing edges */ public Iterable, Node>> getOutgoingEdges(Node node) { return m.get(node); } @Override public String toString() { String s = "{\n\troot=" + root + "\n"; for (Map.Entry e : m.entrySet()) { s += "\t" + e.getKey() + " -> " + e.getValue() + "\n"; } return s + "}\n"; } /** * Turn a pattern-matching computation into a tree representing that computation. * * @param k The computation * @return return The node at the root of that tree */ private Node build(PatternMatchingComputation k) { if (k.isFinished()) { return new Leaf(k.getFinalTerm()); } Node node = new InternalNode(k.getCurrentSymbolicTerm()); List, Node>> outgoing = new ArrayList<>(); for (Pair, PatternMatchingComputation> p : k.stepComputation()) { outgoing.add(new Pair<>(p.fst(), build(p.snd()))); } m.put(node, outgoing); return node; } /** * This interface represents a term that is being matched upon; it is symbolic, in that at compile * time we might not know its actual concrete value. */ public static interface SymbolicTerm {} /** This class represents the scrutinee of the match expression. */ public static enum BaseSymbolicTerm implements SymbolicTerm { INSTANCE; @Override public String toString() { return "B"; } } /** * The class represents a symbolic term resulting from indexing into the arguments of another * symbolic term. */ public static class DerivedSymbolicTerm implements SymbolicTerm { private final SymbolicTerm base; private final int index; private DerivedSymbolicTerm(SymbolicTerm base, int index) { this.base = base; this.index = index; } public SymbolicTerm getBase() { return base; } public int getIndex() { return index; } @Override public String toString() { return base + "[" + index + "]"; } } /** * This class represents a pattern-matching computation; i.e., the logic that happens when you try * to match a scrutinee against a sequence of (pattern => expression) pairs. */ private static class PatternMatchingComputation { /** * This field keeps track of all the symbolic terms that we are trying to match against * patterns. */ private final Deque schema; /** * This is an iterable representing all the computation that remains to be done. It consists of * pairs. The first element of the pair is a deque of pattern terms; this deque should be the * same size as the {@link #schema schema} field, and symbolic terms are pairwise matched * against terms in this deque. The second element of the pair is the computation that happens * if a complete pattern is matched (i.e., it is the right-hand side of a (pattern => * expression) pair). */ private final Iterable, Term>> continuation; private PatternMatchingComputation( Deque schema, Iterable, Term>> continuation) { this.schema = schema; this.continuation = continuation; } /** * Construct a pattern-matching computation from a list of (pattern => expression) pairs. * * @param clauses The (pattern => expression) pairs * @return the pattern-matching computation */ public static PatternMatchingComputation mk(List> clauses) { /* * Initially, the schema is just the symbolic term representing the entire * scrutinee, and the continuation represents the ordered (pattern => * expression) pairs. */ Deque schema = new ArrayDeque<>(); schema.add(BaseSymbolicTerm.INSTANCE); List, Term>> continuation = new ArrayList<>(); for (Pair clause : clauses) { ArrayDeque k = new ArrayDeque<>(); k.add(clause.fst()); continuation.add(new Pair<>(k, clause.snd())); } return new PatternMatchingComputation(schema, continuation); } /** * Return the pattern-matching logic that follows the current logic. * * @return An iterable of pairs of edges and pattern-matching computations. The pairs are in * order of priority (high to low); the pattern-matching computation for an edge represents * the computation that follows the logic represented by that edge. */ public Iterable, PatternMatchingComputation>> stepComputation() { assert !isFinished(); /* First, create new edges corresponding to the next step of the computation. */ List, Set, Term>>>> l = new ArrayList<>(); Edge lastEdge = null; Set, Term>> lastSet = null; for (Pair, Term> p : continuation) { Deque d = new ArrayDeque<>(p.fst()); Edge edge = step(d); p = new Pair<>(d, p.snd()); if (edge.equals(lastEdge)) { lastSet.add(p); } else { lastEdge = edge; lastSet = new LinkedHashSet<>(); lastSet.add(p); l.add(new Pair<>(lastEdge, lastSet)); } } /* * Second, create new pattern-matching computations representing the rest of the * work to do along each edge. */ List, PatternMatchingComputation>> l2 = new ArrayList<>(); for (Pair, Set, Term>>> p : l) { Edge edge = p.fst(); Deque newSchema = new ArrayDeque<>(schema); SymbolicTerm base = newSchema.removeFirst(); if (edge instanceof CtorEdge) { /* * If we're matching against a constructor symbol, we need to also match the * arguments of the current scrutinee against its arguments. */ ConstructorSymbol sym = ((CtorEdge) edge).getLabel(); for (int i = sym.getArity() - 1; i >= 0; --i) { newSchema.addFirst(new DerivedSymbolicTerm(base, i)); } } PatternMatchingComputation k = new PatternMatchingComputation(newSchema, p.snd()); l2.add(new Pair<>(edge, k)); } return l2; } /** * Create an edge corresponding to the pattern matching logic for the first step in processing a * deque of patterns. The provided deque is also updated. * * @param d The deque of patterns * @return The edge */ private static Edge step(Deque d) { Term next = d.removeFirst(); Edge edge = next.accept(tv, null); if (next instanceof Constructor) { /* * If we are matching against a constructor, then we need to recursively match * against its arguments */ Constructor ctor = (Constructor) next; Term[] args = ctor.getArgs(); for (int i = args.length - 1; i >= 0; --i) { d.addFirst(args[i]); } } return edge; } /** * Get the symbolic term that we are currently trying to match against a pattern. * * @return The symbolic term */ public SymbolicTerm getCurrentSymbolicTerm() { assert !isFinished(); return schema.getFirst(); } /** * Return true iff there are no more computational steps to take. * * @return True iff there are no more computational steps to take */ public boolean isFinished() { return schema.isEmpty(); } /** * Return the expression to evaluate if the current pattern succeeds. * * @return The expression */ public Term getFinalTerm() { assert isFinished(); return continuation.iterator().next().snd(); } @Override public String toString() { return "PatternMatchingComputation [schema=" + schema + ", continuation=" + continuation + "]"; } } /** * This field is used to turn a term into an edge corresponding to the first step of logic * necessary for matching against that term. */ private static final TermVisitor> tv = new TermVisitor>() { @Override public Edge visit(Var x, Void in) { return new VarEdge(x); } @Override public Edge visit(Constructor c, Void in) { return new CtorEdge(c.getSymbol()); } @Override public Edge visit(Primitive p, Void in) { return new PrimEdge(p); } @Override public Edge visit(Expr e, Void in) { // Expressions cannot occur in patterns throw new AssertionError("impossible"); } }; /** This interface represents a node in the pattern match tree. */ public static interface Node { O accept(NodeVisitor visitor, I in); } public static interface NodeVisitor { O visit(InternalNode node, I in); O visit(Leaf node, I in); } /** The class represents an internal (i.e., non-leaf) node in the tree. */ public static class InternalNode implements Node { private static AtomicInteger cnt = new AtomicInteger(); private final SymbolicTerm symbolicTerm; private final int id = cnt.getAndIncrement(); public InternalNode(SymbolicTerm symbolicTerm) { this.symbolicTerm = symbolicTerm; } public SymbolicTerm getSymbolicTerm() { return symbolicTerm; } @Override public O accept(NodeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public String toString() { return "Node#" + id + "(" + symbolicTerm + ")"; } } /** * The class represents a leaf of the tree; it holds the computation on the right-hand side of a * (pattern => expression) pair. */ public static class Leaf implements Node { private final Term term; public Leaf(Term term) { this.term = term; } public Term getTerm() { return term; } @Override public O accept(NodeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public String toString() { return "Leaf(" + term + ")"; } } /** * This interface represents an edge in the tree. Edges correspond to pattern matching logic * (i.e., binding a variable, or checking a constant or constructor), and this interface is * parametric in the type of computation that needs to be performed. * * @param The type of the edge */ public static interface Edge { T getLabel(); O accept(EdgeVisitor visitor, I in); } public static interface EdgeVisitor { O visit(VarEdge e, I in); O visit(PrimEdge e, I in); O visit(CtorEdge e, I in); } private abstract static class AbstractEdge implements Edge { protected final T label; public AbstractEdge(T label) { this.label = label; } @Override public T getLabel() { return label; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((label == null) ? 0 : label.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("rawtypes") AbstractEdge other = (AbstractEdge) obj; if (label == null) { if (other.label != null) return false; } else if (!label.equals(other.label)) return false; return true; } } /** This edge represents pattern matching logic in which a variable is bound. */ public static class VarEdge extends AbstractEdge { public VarEdge(Var label) { super(label); } @Override public O accept(EdgeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public String toString() { return "VarEdge(" + label + ")"; } } /** This edge represents pattern matching logic in which a primitive constant is checked. */ public static class PrimEdge extends AbstractEdge> { public PrimEdge(Primitive label) { super(label); } @Override public O accept(EdgeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public String toString() { return "PrimEdge(" + label + ")"; } } /** This edge represents pattern matching logic in which a constructor symbol is checked. */ public static class CtorEdge extends AbstractEdge { public CtorEdge(ConstructorSymbol label) { super(label); } @Override public O accept(EdgeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public String toString() { return "CtorEdge(" + label + ")"; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/QueryPlanner.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRule; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.validating.Stratum; import java.util.List; public interface QueryPlanner { List> genPlan(SRule r, Stratum stratum); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/RuleTranslator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SAtom; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SDestructorBody; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SExprBody; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SFunctorCall; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SInfixBinaryOpAtom; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SInt; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SLit; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRule; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SRuleMode; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.STerm; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.SVar; import edu.harvard.seas.pl.formulog.eval.SmtCallFinder; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import edu.harvard.seas.pl.formulog.validating.Stratifier; import edu.harvard.seas.pl.formulog.validating.Stratum; import edu.harvard.seas.pl.formulog.validating.ValidRule; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteralVisitor; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; import edu.harvard.seas.pl.formulog.validating.ast.SimpleRule; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class RuleTranslator { private final CodeGenContext ctx; private final QueryPlanner queryPlanner; private static final String checkPred = "CODEGEN_CHECK"; private static int smtSupRelCount = 0; public RuleTranslator(CodeGenContext ctx, QueryPlanner queryPlanner) { this.ctx = ctx; this.queryPlanner = queryPlanner; } public Pair, List> translate(BasicProgram prog) throws CodeGenException { var strats = checkStratification(prog); List l = new ArrayList<>(); Worker worker = new Worker(prog); for (var stratum : strats) { for (RelationSymbol sym : stratum.getPredicateSyms()) { for (BasicRule r : prog.getRules(sym)) { for (var sr : worker.translate(r)) { sr.setQueryPlan(queryPlanner.genPlan(sr, stratum)); l.add(sr); } } } } l.addAll(worker.createProjectionRules()); l.add(worker.createCheckRule()); return new Pair<>(l, strats); } private List checkStratification(BasicProgram prog) throws CodeGenException { Stratifier stratifier = new Stratifier(prog); List strats; try { strats = stratifier.stratify(); } catch (InvalidProgramException e) { throw new CodeGenException(e); } for (Stratum strat : strats) { if (strat.hasRecursiveNegationOrAggregation()) { throw new CodeGenException("Cannot handle recursive negation or aggregation: " + strat); } } return strats; } private class Worker { private final BasicProgram prog; private Map varCount; private final Set>> projections = new HashSet<>(); private final SmtCallFinder scf = new SmtCallFinder(); public Worker(BasicProgram prog) { this.prog = prog; } private List translate(BasicRule br) throws CodeGenException { SimpleRule sr; try { ValidRule vr = ValidRule.make(br, (_lit, _vars) -> 1); sr = SimpleRule.make(vr, prog.getFunctionCallFactory()); varCount = sr.countVariables(); } catch (InvalidProgramException e) { throw new CodeGenException(e); } List> bodies = new ArrayList<>(); List curBody = new ArrayList<>(); List> vars = new ArrayList<>(); Set curVars = new HashSet<>(); for (int i = 0; i < sr.getBodySize(); ++i) { var lit = sr.getBody(i); if (Configuration.codegenSplitOnSmt && curBody.size() > 1 && scf.containsSmtCall(lit)) { bodies.add(curBody); curBody = new ArrayList<>(); vars.add(curVars); curVars = new HashSet<>(curVars); } var lits = translate(lit); curBody.addAll(lits); addVars(lits, curVars); } var head = sr.getHead(); if (Configuration.codegenSplitOnSmt && curBody.size() > 1 && scf.containsSmtCall(head)) { bodies.add(curBody); curBody = new ArrayList<>(); vars.add(curVars); curVars = new HashSet<>(curVars); } bodies.add(curBody); vars.add(curVars); List headTrans = translate(head); assert headTrans.size() == 1; var newHead = headTrans.get(0); var liveVars = liveVarsByBody(newHead, bodies); List rules = new ArrayList<>(); SAtom prevSup = null; for (int i = 0; i < bodies.size(); ++i) { var body = bodies.get(i); if (prevSup != null) { body.add(0, prevSup); } if (i < bodies.size() - 1) { var presentVars = vars.get(i); var varsToKeep = presentVars.stream() .filter(liveVars.get(i + 1)::contains) .collect(Collectors.toList()); var supAtom = newSmtSupAtom(varsToKeep); rules.add(new SRule(supAtom, body)); prevSup = supAtom; } else { rules.add(new SRule(newHead, body)); } } return rules; } void addVars(Collection lits, Set vars) { for (var lit : lits) { lit.varSet(vars); } } List> liveVarsByBody(SLit head, List> bodies) { List> l = new ArrayList<>(); Set vars = head.varSet(); l.add(vars); for (var it = bodies.listIterator(bodies.size()); it.hasPrevious(); ) { vars = new HashSet<>(vars); addVars(it.previous(), vars); l.add(vars); } Collections.reverse(l); return l; } private SAtom newSmtSupAtom(Collection vars) { var name = "CODEGEN_SMT_SUP_" + smtSupRelCount++; ctx.registerCustomRelation(name, vars.size(), SRuleMode.OUTPUT); return new SAtom(name, new ArrayList<>(vars), false); } private List translate(SimpleLiteral lit) { return lit.accept( new SimpleLiteralVisitor>() { @Override public List visit(Assignment assignment, Void input) { STerm lhs = translate(assignment.getDef()); STerm rhs = translate(assignment.getVal()); return Collections.singletonList(new SInfixBinaryOpAtom(lhs, "=", rhs)); } @Override public List visit(Check check, Void input) { STerm lhs = translate(check.getLhs()); STerm rhs = translate(check.getRhs()); return Collections.singletonList( new SInfixBinaryOpAtom(lhs, check.isNegated() ? "!=" : "=", rhs)); } @Override public List visit(Destructor destructor, Void input) { List l = new ArrayList<>(); Term scrutinee = destructor.getScrutinee(); STerm translatedScrutineeExpr = translate(scrutinee); Var scrutineeVar = Var.fresh(); STerm translatedScrutineeVar = new SVar(scrutineeVar); l.add(new SInfixBinaryOpAtom(translatedScrutineeVar, "=", translatedScrutineeExpr)); List args = Collections.singletonList(scrutineeVar); String functor = ctx.lookupOrCreateFunctorName(destructor.getSymbol()); var dtor = new SDestructorBody(args, scrutineeVar, destructor.getSymbol()); ctx.registerFunctorBody(functor, dtor); List translatedArgs = args.stream().map(Worker.this::translate).collect(Collectors.toList()); assert translatedArgs.size() == 1 && translatedArgs.get(0) instanceof SVar; STerm lhs = new SFunctorCall(functor, translatedArgs); SVar recRef = new SVar(Var.fresh()); l.add(new SInfixBinaryOpAtom(lhs, "=", recRef)); SVar checkOutput = new SVar(Var.fresh()); l.add(new SAtom(checkPred, Arrays.asList(recRef, checkOutput), false)); int arity = destructor.getSymbol().getArity(); Var[] bindings = destructor.getBindings(); for (int i = 0; i < arity; ++i) { SFunctorCall call = new SFunctorCall("nth", new SInt(i), translatedArgs.get(0), checkOutput); l.add(new SInfixBinaryOpAtom(translate(bindings[i]), "=", call)); } return l; } @Override public List visit(SimplePredicate predicate, Void input) { List args = Arrays.stream(predicate.getArgs()) .map(Worker.this::translate) .collect(Collectors.toList()); SLit lit; if (predicate.isNegated()) { lit = handleNegatedPredicate(predicate, args); } else { String symbol = ctx.lookupRepr(predicate.getSymbol()); lit = new SAtom(symbol, args, false); } return Collections.singletonList(lit); } }, null); } private SLit handleNegatedPredicate(SimplePredicate predicate, List souffleArgs) { List projection = new ArrayList<>(); int ignoredCount = 0; var args = predicate.getArgs(); for (Term arg : args) { if (arg instanceof Var && varCount.get((Var) arg) == 1) { ignoredCount++; projection.add(false); } else { projection.add(true); } } RelationSymbol sym = predicate.getSymbol(); if (ignoredCount == 0) { return new SAtom(ctx.lookupRepr(sym), souffleArgs, true); } projections.add(new Pair<>(predicate.getSymbol(), projection)); List newArgs = new ArrayList<>(); for (int i = 0; i < souffleArgs.size(); ++i) { if (projection.get(i)) { newArgs.add(souffleArgs.get(i)); } } return new SAtom(createProjectionSymbol(sym, projection), newArgs, true); } private String createProjectionSymbol(RelationSymbol sym, List projection) { StringBuilder sb = new StringBuilder(); sb.append("CODEGEN_PROJECT_"); sb.append(ctx.lookupRepr(sym)); for (Boolean retain : projection) { sb.append(retain ? "1" : "0"); } return sb.toString(); } private SFunctorCall liftToFunctor(Term t) { String functor = ctx.lookupOrCreateFunctorName(t); List args = new ArrayList<>(t.varSet()); ctx.registerFunctorBody(functor, new SExprBody(args, t)); List translatedArgs = args.stream().map(this::translate).collect(Collectors.toList()); return new SFunctorCall(functor, translatedArgs); } private STerm translate(Term t) { return t.accept( new Terms.TermVisitor() { @Override public STerm visit(Var t, Void in) { return new SVar(t); } @Override public STerm visit(Constructor c, Void in) { return liftToFunctor(c); } @Override public STerm visit(Primitive p, Void in) { return liftToFunctor(p); } @Override public STerm visit(Expr e, Void in) { return liftToFunctor(e); } }, null); } List createProjectionRules() { List l = new ArrayList<>(); for (var p : projections) { l.add(createProjection(p.fst(), p.snd())); } return l; } private SRule createProjection(RelationSymbol sym, List projection) { List headArgs = new ArrayList<>(); List bodyArgs = new ArrayList<>(); for (int i = 0; i < sym.getArity(); ++i) { STerm arg = new SVar(Var.fresh("x" + i)); bodyArgs.add(arg); if (projection.get(i)) { headArgs.add(arg); } } String projSym = createProjectionSymbol(sym, projection); SLit head = new SAtom(projSym, headArgs, false); SLit bodyAtom = new SAtom(ctx.lookupRepr(sym), bodyArgs, false); ctx.registerCustomRelation(projSym, headArgs.size(), SRuleMode.INTERMEDIATE); return new SRule(head, bodyAtom); } public SRule createCheckRule() { ctx.registerCustomRelation(checkPred, 2, SRuleMode.INTERMEDIATE); SLit head = new SAtom(checkPred, Arrays.asList(new SInt(1), new SInt(1)), false); return new SRule(head); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/SmtParserCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppBinop; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppConst; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppDecl; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppExpr; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppExprFromString; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppFuncCall; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppIf; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppReturn; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppSeq; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppStmt; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppVar; import edu.harvard.seas.pl.formulog.smt.SmtLibParser; import edu.harvard.seas.pl.formulog.smt.SmtLibParser.SmtLibParseException; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class SmtParserCpp extends TemplateSrcFile { public SmtParserCpp(CodeGenContext ctx) { super("smt_parser.cpp", ctx); } @Override void gen(BufferedReader br, PrintWriter out) throws IOException, CodeGenException { Worker w = new Worker(out); CodeGenUtil.copyOver(br, out, 0); w.outputShouldRecord(); CodeGenUtil.copyOver(br, out, 1); w.outputParseFuncs(); CodeGenUtil.copyOver(br, out, 2); w.outputParseTerm(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final PrintWriter out; private final Set trackedSmtVars = new HashSet<>(); private final Map parseFuncs = new HashMap<>(); private final Map parseFuncDefs = new HashMap<>(); public Worker(PrintWriter out) { this.out = out; parseFuncs.put(BuiltInTypes.string, "parse_string"); parseFuncs.put(BuiltInTypes.i32, "parse_i32"); parseFuncs.put(BuiltInTypes.i64, "parse_i64"); parseFuncs.put(BuiltInTypes.fp32, "parse_fp32"); parseFuncs.put(BuiltInTypes.fp64, "parse_fp64"); parseFuncs.put(BuiltInTypes.bool, "parse_bool"); } private void outputShouldRecord() throws CodeGenException { for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { var ty = getSmtVarType(sym); if (ty != null) { try { if (SmtLibParser.shouldRecord(ty)) { out.printf(" case %s: return true;\n", ctx.lookupRepr(sym)); trackedSmtVars.add(sym); } } catch (SmtLibParseException e) { throw new CodeGenException(e); } } } } private void outputParseFuncs() { for (ConstructorSymbol sym : trackedSmtVars) { genParseFunc(getSmtVarType(sym)); } // Forward-declare functions to account for mutually recursive data types. for (String name : parseFuncDefs.keySet()) { out.println("struct " + name + ";"); } out.println(); for (Map.Entry e : parseFuncDefs.entrySet()) { out.println("struct " + e.getKey() + " {"); out.println(" term_ptr operator()(SmtLibTokenizer &t) {"); e.getValue().println(out, 2); out.println(" }\n};\n"); } } private String genParseFunc(AlgebraicDataType ty) { String name = parseFuncs.get(ty); if (name != null) { return name; } name = "parse_" + ctx.newId(CodeGenUtil.mkName(ty.getSymbol())); var wrapped_name = "parse_adt<" + name + ">"; parseFuncs.put(ty, wrapped_name); List body = new ArrayList<>(); String scrutinee = "x"; body.add(CppDecl.mk(scrutinee, CppFuncCall.mk("parse_identifier", CppVar.mk("t")))); List> cases = new ArrayList<>(); for (var cs : ty.getConstructors()) { cases.add(genConstructorCase(CppVar.mk(scrutinee), cs)); } body.add(CppIf.mk(cases)); body.add(CppExprFromString.mk("abort()").toStmt()); parseFuncDefs.put(name, CppSeq.mk(body)); return wrapped_name; } private Pair genConstructorCase(CppExpr scrutinee, ConstructorScheme cs) { ConstructorSymbol sym = cs.getSymbol(); var check = CppBinop.mkEq(scrutinee, CppConst.mkString(sym.toString())); List args = new ArrayList<>(); List stmts = new ArrayList<>(); int i = 0; for (var ty : cs.getTypeArgs()) { String arg = "a" + i; CppStmt decl = CppDecl.mk(arg, CppFuncCall.mk(genParseFunc((AlgebraicDataType) ty), CppVar.mk("t"))); stmts.add(decl); args.add(CppVar.mk(arg)); i++; } CppExpr term = CppFuncCall.mk("Term::make<" + ctx.lookupRepr(sym) + ">", args); stmts.add(CppReturn.mk(term)); return new Pair<>(check, CppSeq.mk(stmts)); } private void outputParseTerm() { for (ConstructorSymbol sym : trackedSmtVars) { var ty = getSmtVarType(sym); var func = parseFuncs.get(ty); out.printf(" case %s: return %s(t);\n", ctx.lookupRepr(sym), func); } } private AlgebraicDataType getSmtVarType(ConstructorSymbol sym) { if (sym instanceof ParameterizedConstructorSymbol) { var base = ((ParameterizedConstructorSymbol) sym).getBase(); if (base.equals(BuiltInConstructorSymbolBase.SMT_VAR)) { return SmtLibParser.stripSymType( (AlgebraicDataType) sym.getCompileTimeType().getRetType()); } } return null; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/SmtShimCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.smt.SmtLibShim; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.HashSet; import java.util.Set; public class SmtShimCpp extends TemplateSrcFile { public SmtShimCpp(CodeGenContext ctx) { super("smt_shim.cpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker pr = new Worker(out); CodeGenUtil.copyOver(br, out, 0); pr.copyDeclarations(); CodeGenUtil.copyOver(br, out, 1); pr.genSolverVarCases(); CodeGenUtil.copyOver(br, out, 2); pr.genNeedsTypeAnnotationCases(); CodeGenUtil.copyOver(br, out, 3); pr.genSerializationCases(); CodeGenUtil.copyOver(br, out, 4); pr.genSymbolSerializationCases(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final PrintWriter out; private final Set varSymbols = new HashSet<>(); public Worker(PrintWriter out) { this.out = out; for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { if (isVarSymbol(sym)) { varSymbols.add(sym); } } } private boolean isVarSymbol(ConstructorSymbol sym) { return sym instanceof ParameterizedConstructorSymbol && ((ParameterizedConstructorSymbol) sym).getBase() == BuiltInConstructorSymbolBase.SMT_VAR; } public void copyDeclarations() { SmtLibShim shim = new SmtLibShim(null, out); shim.initialize(ctx.getProgram(), true); try { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); } catch (EvaluationException e) { throw new AssertionError("impossible"); } } public void genSolverVarCases() { if (varSymbols.isEmpty()) { return; } for (ConstructorSymbol sym : varSymbols) { out.println(" case " + ctx.lookupRepr(sym) + ":"); } CppReturn.mk(CppConst.mkTrue()).println(out, 3); } public void genSerializationCases() { for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { genSerializationCase(sym); } } private void genSerializationCase(ConstructorSymbol sym) { CppStmt stmt = genSerializationCaseBody(sym); if (stmt == null) { return; } out.println(" case " + ctx.lookupRepr(sym) + ": {"); stmt.println(out, 3); out.println(" break;"); out.println(" }"); } private CppStmt genSerializationCaseBody(ConstructorSymbol sym) { if (sym instanceof BuiltInConstructorSymbol) { return genBuiltInConstructorSymbolCase((BuiltInConstructorSymbol) sym); } if (sym instanceof ParameterizedConstructorSymbol) { return genParameterizedConstructorSymbolCase((ParameterizedConstructorSymbol) sym); } return null; } private CppStmt genBuiltInConstructorSymbolCase(BuiltInConstructorSymbol sym) { switch (sym) { case ARRAY_CONST: return genSerializeOp("const"); case ARRAY_STORE: return genSerializeOp("store"); case BV_ADD: return genSerializeOp("bvadd"); case BV_AND: return genSerializeOp("bvand"); case BV_MUL: return genSerializeOp("bvmul"); case BV_NEG: return genSerializeOp("bvneg"); case BV_OR: return genSerializeOp("bvor"); case BV_SDIV: return genSerializeOp("bvsdiv"); case BV_SREM: return genSerializeOp("bvsrem"); case BV_SUB: return genSerializeOp("bvsub"); case BV_UDIV: return genSerializeOp("bvudiv"); case BV_UREM: return genSerializeOp("bvurem"); case BV_XOR: return genSerializeOp("bvxor"); case BV_SHL: return genSerializeOp("bvshl"); case BV_LSHR: return genSerializeOp("bvlshr"); case BV_ASHR: return genSerializeOp("bvashr"); // Normal constructors don't need special treatment case CMP_EQ: case CMP_GT: case CMP_LT: case CONS: case NIL: case NONE: case SOME: break; case ENTER_FORMULA: case EXIT_FORMULA: return CppFuncCall.mk("abort").toStmt(); case FP_ADD: return genSerializeOp("fp.add"); case FP_DIV: return genSerializeOp("fp.div"); case FP_MUL: return genSerializeOp("fp.mul"); case FP_NEG: return genSerializeOp("fp.neg"); case FP_REM: return genSerializeOp("fp.rem"); case FP_SUB: return genSerializeOp("fp.sub"); case INT_ABS: return genSerializeOp("abs"); case INT_ADD: return genSerializeOp("+"); case INT_BIG_CONST: return genSerializeInt(true); case INT_CONST: return genSerializeInt(false); case INT_DIV: return genSerializeOp("div"); case INT_GE: return genSerializeOp(">="); case INT_GT: return genSerializeOp(">"); case INT_LE: return genSerializeOp("<="); case INT_LT: return genSerializeOp("<"); case INT_MOD: return genSerializeOp("mod"); case INT_MUL: return genSerializeOp("*"); case INT_NEG: case INT_SUB: return genSerializeOp("-"); case SMT_AND: return genSerializeOp("and"); case SMT_EXISTS: return genSerializeQuantifier(true); case SMT_FORALL: return genSerializeQuantifier(false); case SMT_IMP: return genSerializeOp("=>"); case SMT_ITE: return genSerializeOp("ite"); case SMT_NOT: return genSerializeOp("not"); case SMT_OR: return genSerializeOp("or"); case STR_AT: return genSerializeOp("str.at"); case STR_CONCAT: return genSerializeOp("str.++"); case STR_CONTAINS: return genSerializeOp("str.contains"); case STR_INDEXOF: return genSerializeOp("str.indexof"); case STR_LEN: return genSerializeOp("str.len"); case STR_PREFIXOF: return genSerializeOp("str.prefixof"); case STR_REPLACE: return genSerializeOp("str.replace"); case STR_SUBSTR: return genSerializeOp("str.substr"); case STR_SUFFIXOF: return genSerializeOp("str.suffixof"); } return null; } private int index(ParameterizedConstructorSymbol sym, int i) { return ((TypeIndex) sym.getArgs().get(i).getType()).getIndex(); } private CppStmt mkCall(String func) { return CppFuncCall.mk(func, CppVar.mk("t")).toStmt(); } private CppStmt genParameterizedConstructorSymbolCase(ParameterizedConstructorSymbol sym) { switch (sym.getBase()) { case ARRAY_DEFAULT: return genSerializeOp("default"); case ARRAY_SELECT: return genSerializeOp("select"); case BV_BIG_CONST: return genSerializeBitString(index(sym, 0), true); case BV_CONST: return genSerializeBitString(index(sym, 0), false); case BV_SGE: return genSerializeOp("bvsge"); case BV_SGT: return genSerializeOp("bvsgt"); case BV_SLE: return genSerializeOp("bvsle"); case BV_SLT: return genSerializeOp("bvslt"); case BV_TO_BV_SIGNED: return genSerializeBvToBv(index(sym, 0), index(sym, 1), true); case BV_TO_BV_UNSIGNED: return genSerializeBvToBv(index(sym, 0), index(sym, 1), false); case BV_EXTRACT: return mkCall("serialize_bv_extract"); case BV_CONCAT: return genSerializeOp("concat"); case BV_TO_FP: { int exp = index(sym, 1); int sig = index(sym, 2); String func = "serialize_bv_to_fp<" + exp + ", " + sig + ">"; return mkCall(func); } case BV_UGE: return genSerializeOp("bvuge"); case BV_UGT: return genSerializeOp("bvugt"); case BV_ULE: return genSerializeOp("bvule"); case BV_ULT: return genSerializeOp("bvult"); case FP_BIG_CONST: { int exp = index(sym, 0); int sig = index(sym, 1); return genSerializeFp(exp, sig, true); } case FP_CONST: { int exp = index(sym, 0); int sig = index(sym, 1); return genSerializeFp(exp, sig, false); } case FP_EQ: return genSerializeOp("fp.eq"); case FP_GE: return genSerializeOp("fp.geq"); case FP_GT: return genSerializeOp("fp.gt"); case FP_IS_NAN: return genSerializeOp("fp.isNaN"); case FP_LE: return genSerializeOp("fp.leq"); case FP_LT: return genSerializeOp("fp.lt"); case FP_TO_SBV: return genSerializeFpToBv(index(sym, 2), true); case FP_TO_UBV: return genSerializeFpToBv(index(sym, 2), false); case FP_TO_FP: { int exp = index(sym, 2); int sig = index(sym, 3); String func = "serialize_fp_to_fp<" + exp + ", " + sig + ">"; return mkCall(func); } case SMT_EQ: return genSerializeOp("="); case SMT_LET: return mkCall("serialize_let"); case SMT_VAR: { CppExpr call = CppFuncCall.mk("lookup_var", CppVar.mk("t")); return CppBinop.mkShiftLeft(CppVar.mk("m_in"), call).toStmt(); } case SMT_PAT: case SMT_WRAP_VAR: return CppFuncCall.mk("abort").toStmt(); case BV_TO_INT: return genSerializeOp("bv2int"); case INT_TO_BV: { int width = index(sym, 0); String func = "serialize_int2bv<" + width + ">"; return mkCall(func); } } return null; } private CppStmt genSerializeOp(String op) { CppExpr s = CppConst.mkString(op); CppExpr t = CppMethodCall.mkThruPtr(CppVar.mk("t"), "as_complex"); return CppFuncCall.mk("serialize", s, t).toStmt(); } private CppStmt genSerializeBitString(int n, boolean big) { CppExpr array = CppAccess.mk(CppMethodCall.mkThruPtr(CppVar.mk("t"), "as_complex"), "val"); CppExpr base = CppSubscript.mk(array, CppConst.mkInt(0)); String type = big ? "int64_t" : "int32_t"; String func = "serialize_bit_string<" + type + ", " + n + ">"; CppExpr arg = CppAccess.mk(CppMethodCall.mkThruPtr(base, "as_base<" + type + ">"), "val"); return CppFuncCall.mk(func, arg).toStmt(); } private CppStmt genSerializeBvToBv(int from, int to, boolean signed) { String func = "serialize_bv_to_bv<" + from + ", " + to + ", " + signed + ">"; return mkCall(func); } private CppStmt genSerializeFpToBv(int width, boolean signed) { String func = "serialize_fp_to_bv<" + width + ", " + signed + ">"; return mkCall(func); } private CppStmt genSerializeFp(int e, int s, boolean big) { String type = big ? "double" : "float"; String func = "serialize_fp<" + type + ", " + e + ", " + s + ">"; CppExpr arg = CppFuncCall.mk("arg0", CppVar.mk("t")); return CppFuncCall.mk(func, arg).toStmt(); } private CppStmt genSerializeInt(boolean big) { String type = big ? "int64_t" : "int32_t"; String func = "serialize_int<" + type + ">"; return mkCall(func); } private CppStmt genSerializeQuantifier(boolean exists) { String func = "serialize_quantifier<" + exists + ">"; return mkCall(func); } public void genNeedsTypeAnnotationCases() { boolean foundOne = false; for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { if (SmtLibShim.needsTypeAnnotation(sym)) { out.println(" case " + ctx.lookupRepr(sym) + ":"); foundOne = true; } } if (foundOne) { CppReturn.mk(CppConst.mkTrue()).println(out, 3); } } public void genSymbolSerializationCases() { boolean foundOne = false; for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { if (sym.getConstructorSymbolType() .equals(ConstructorSymbolType.SOLVER_CONSTRUCTOR_TESTER)) { out.println(" case " + ctx.lookupRepr(sym) + ":"); foundOne = true; } } if (foundOne) { CppExpr call = CppFuncCall.mk("serialize_tester", CppVar.mk("sym")); CppReturn.mk(call).println(out, 3); } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/SouffleCodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.codegen.ast.souffle.*; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.validating.Stratum; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class SouffleCodeGen { private final CodeGenContext ctx; private static final String non_existent_input = "CODEGEN_IMPOSSIBLE_IN"; private static final String non_existent_output = "CODEGEN_IMPOSSIBLE_OUT"; private static final SAtom non_existent_input_atom = new SAtom(non_existent_input, Collections.emptyList(), false); public SouffleCodeGen(CodeGenContext ctx) { this.ctx = ctx; } public void gen(File directory) throws CodeGenException, IOException { QueryPlanner qp = chooseQueryPlanner(); Pair, List> p = new RuleTranslator(ctx, qp).translate(ctx.getProgram()); var rules = p.fst(); ctx.registerCustomRelation(non_existent_input, 0, SRuleMode.INPUT); ctx.registerCustomRelation(non_existent_output, 0, SRuleMode.OUTPUT); rules.add(genRuleForRetainingInputRelations()); rules.addAll(genRulesForEnforcingStratification(p.snd())); File dlFile = directory.toPath().resolve(Path.of("src", "formulog.dl")).toFile(); emitDlFile(dlFile, rules); new FunctorCodeGen(ctx).emitFunctors(directory); } private QueryPlanner chooseQueryPlanner() throws CodeGenException { switch (Configuration.optimizationSetting) { case 0: return new NopQueryPlanner(); case 5: return new DeltaFirstQueryPlanner(ctx); default: throw new CodeGenException( "Unsupported optimization setting for codegen: " + Configuration.optimizationSetting); } } private void emitDlFile(File dlFile, List rules) throws CodeGenException { PrintWriter writer; try { writer = new PrintWriter(dlFile); } catch (FileNotFoundException e) { throw new CodeGenException(e); } Worker worker = new Worker(writer); worker.declareTypes(); writer.println(); worker.declareRelations(); writer.println(); worker.declareFunctors(); writer.println(); for (SRule rule : rules) { writer.println(rule); } writer.close(); } SRule genRuleForRetainingInputRelations() { SAtom head = new SAtom(non_existent_output, Collections.emptyList(), false); List body = new ArrayList<>(); body.add(non_existent_input_atom); for (RelationSymbol sym : ctx.getProgram().getFactSymbols()) { body.add(makeAtomWithDummyArgs(sym, false)); } return new SRule(head, body); } private List genRulesForEnforcingStratification(List strats) { if (strats.isEmpty()) { return Collections.emptyList(); } List l = new ArrayList<>(); RelationSymbol firstRepr = strats.get(0).getPredicateSyms().iterator().next(); var prevAtom = makeAtomWithDummyArgs(firstRepr, true); for (int i = 1; i < strats.size(); ++i) { RelationSymbol currRepr = strats.get(i).getPredicateSyms().iterator().next(); var currAtom = makeAtomWithDummyArgs(currRepr, false); l.add(new SRule(currAtom, non_existent_input_atom, prevAtom)); prevAtom = currAtom; } return l; } private SAtom makeAtomWithDummyArgs(RelationSymbol sym, boolean negated) { List args = new ArrayList<>(); for (int i = 0; i < sym.getArity(); ++i) { args.add(new SInt(0)); } return new SAtom(ctx.lookupRepr(sym), args, negated); } private class Worker { private final PrintWriter writer; public Worker(PrintWriter writer) { this.writer = writer; } public void declareTypes() { for (SIntListType ty : ctx.getSouffleTypes()) { writer.print(".type "); writer.print(ty.getName()); writer.print(" = "); writer.println(ty.getDef()); } } public void declareRelations() { for (RelationSymbol sym : ctx.getProgram().getFactSymbols()) { declareRelation(sym, SRuleMode.INPUT); } for (RelationSymbol sym : ctx.getProgram().getRuleSymbols()) { declareRelation(sym, SRuleMode.OUTPUT); } for (Pair> e : ctx.getCustomRelations()) { declareRelation(e.fst(), e.snd().fst(), e.snd().snd()); } } private void declareRelation(String name, int arity, SRuleMode mode) { writer.print(".decl "); writer.print(name); writer.print("("); for (int i = 0; i < arity; ++i) { writer.print("x"); writer.print(i); writer.print(":number"); if (i < arity - 1) { writer.print(", "); } } writer.println(")"); switch (mode) { case INPUT: writer.print(".input "); break; case OUTPUT: writer.print(".output "); break; case INTERMEDIATE: return; } writer.println(name); } private void declareRelation(RelationSymbol sym, SRuleMode mode) { declareRelation(ctx.lookupRepr(sym), sym.getArity(), mode); } public void declareFunctors() { writer.println(".functor nth(n:number, ref:number, check:number):number"); for (Pair p : ctx.getFunctors()) { String name = p.fst(); SFunctorBody body = p.snd(); declareFunctor(name, body); } } private void declareFunctor(String name, SFunctorBody body) { writer.print(".functor "); writer.print(name); writer.print("("); for (int i = 0; i < body.getArity(); ++i) { writer.print("x"); writer.print(i); writer.print(":number"); if (i < body.getArity() - 1) { writer.print(", "); } } writer.print("):"); writer.print(body.getRetType()); if (body.isStateful()) { writer.print(" stateful"); } writer.println(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/SymbolCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager.TupleSymbol; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Set; public class SymbolCpp extends TemplateSrcFile { public SymbolCpp(CodeGenContext ctx) { super("Symbol.cpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker w = new Worker(out); CodeGenUtil.copyOver(br, out, 0); w.defineSerialization(); CodeGenUtil.copyOver(br, out, 1); w.initializeSymbolTable(); CodeGenUtil.copyOver(br, out, 2); w.defineTupleLookup(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final Set symbols = ctx.getConstructorSymbols(); private final PrintWriter out; public Worker(PrintWriter out) { this.out = out; } void defineSerialization() { for (ConstructorSymbol sym : symbols) { out.print(" case "); out.print(ctx.lookupRepr(sym)); out.print(": return out << \""); out.print(sym); out.println("\";"); } } void initializeSymbolTable() { for (ConstructorSymbol sym : symbols) { CppExpr access = CppSubscript.mk(CppVar.mk("symbol_table"), CppConst.mkString(CodeGenUtil.mkName(sym))); CppExpr assign = CppBinop.mkAssign(access, CppVar.mk(ctx.lookupRepr(sym))); assign.toStmt().println(out, 1); } } void defineTupleLookup() { for (ConstructorSymbol sym : symbols) { if (sym instanceof TupleSymbol) { out.print(" case " + sym.getArity() + ": return "); out.print(ctx.lookupRepr(sym)); out.println(";"); } } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/SymbolHpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Set; public class SymbolHpp extends TemplateSrcFile { public SymbolHpp(CodeGenContext ctx) { super("Symbol.hpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker w = new Worker(out); CodeGenUtil.copyOver(br, out, 0); w.declareSymbols(); CodeGenUtil.copyOver(br, out, 1); w.defineArity(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final Set symbols = ctx.getConstructorSymbols(); private final PrintWriter out; public Worker(PrintWriter out) { this.out = out; } void declareSymbols() { for (ConstructorSymbol sym : symbols) { out.print(" "); out.println(ctx.lookupUnqualifiedRepr(sym) + ","); } } void defineArity() { for (ConstructorSymbol sym : symbols) { out.print(" case "); out.print(ctx.lookupRepr(sym)); out.println(": return " + sym.getArity() + ";"); } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/TemplateSrcFile.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import java.io.*; public abstract class TemplateSrcFile { private final String name; protected final CodeGenContext ctx; public TemplateSrcFile(String name, CodeGenContext ctx) { this.name = name; this.ctx = ctx; } void gen(File outDir) throws IOException, CodeGenException { try (InputStream is = CodeGenUtil.inputSrcFile(name); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); PrintWriter out = new PrintWriter(CodeGenUtil.outputSrcFile(outDir, name))) { gen(br, out); out.flush(); } } abstract void gen(BufferedReader br, PrintWriter out) throws IOException, CodeGenException; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/TermCodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; public class TermCodeGen { private final CodeGenContext ctx; public TermCodeGen(CodeGenContext ctx) { this.ctx = ctx; } public CppExpr mkBool(CppExpr bool) { return CppFuncCall.mk("Term::make", bool); } /* * EZ: mkComplex no longer requires supporting statements, but these methods * still exist in order to not break the rest of the code. * * AB: Probably makes sense to keep the current signature - I don't think it * causes any significant harm, and it gives us some future flexibility. */ /** * Generate the C++ representation of a complex term given the symbol of a constructor and its * arguments. * * @param sym * @param args * @return A pair of a C++ statement and a C++ expression representing the complex term; the * statement should be executed before the expression is used. */ public Pair mkComplex(ConstructorSymbol sym, List args) { assert sym.getArity() == args.size(); CppVar symbol = CppVar.mk(ctx.lookupRepr(sym)); CppExpr val = CppFuncCall.mk("Term::make<" + symbol + ">", args); return new Pair<>(CppSeq.mk(), val); } /** * Generate the C++ representation of a complex term given the symbol of a constructor and its * arguments. * * @param sym * @param args * @return A pair of a C++ statement and a C++ expression representing the complex term; the * statement should be executed before the expression is used. */ public Pair mkComplex(ConstructorSymbol sym, CppExpr... args) { return mkComplex(sym, Arrays.asList(args)); } /** * Generate the C++ representation of a complex term given the symbol of a constructor and its * arguments. * * @param acc An accumulator list of statements; these should be executed before the returned * expression is used * @param sym * @param args * @return A C++ expression representing the complex term */ public CppExpr mkComplex(List acc, ConstructorSymbol sym, CppExpr... args) { Pair p = mkComplex(sym, args); acc.add(p.fst()); return p.snd(); } /** * Generate the C++ representation of a complex term given the symbol of a constructor and its * arguments. * * @param acc An accumulator list of statements; these should be executed before the returned * expression is used * @param sym * @param args * @return A C++ expression representing the complex term */ public CppExpr mkComplex(List acc, ConstructorSymbol sym, List args) { Pair p = mkComplex(sym, args); acc.add(p.fst()); return p.snd(); } /** * Generate the C++ representation of a term, given an environment mapping Formulog variables to * C++ expressions. * * @param t * @param env * @return A pair of a C++ statement and a C++ expression representing the term; the statement * should be executed before the expression is used. */ public Pair gen(Term t, Map env) { List acc = new ArrayList<>(); CppExpr e = gen(acc, t, env); return new Pair<>(CppSeq.mk(acc), e); } /** * Generate the C++ representation of a term, given an environement mapping Formulog variables to * C++ expressions. * * @param acc An accumulator list of statements; these should be executed before the returned * expression is used * @param sym * @param args * @return A C++ expression representing the term */ public CppExpr gen(List acc, Term t, Map env) { return new Worker(acc, env).go(t); } /** * Generate the C++ representation of a term, given an environement mapping Formulog variables to * C++ expressions. * * @param acc An accumulator list of statements; these should be executed before the returned * expression is used * @param sym * @param args * @return C++ expressions representing the terms (in order) */ public List gen(List acc, List ts, Map env) { List exprs = new ArrayList<>(); for (Term t : ts) { exprs.add(gen(acc, t, env)); } return exprs; } /** * Generate the C++ representation of some terms, given an environment mapping Formulog variables * to C++ expressions. * * @param ts * @param env * @return A pair of a C++ statement and a list of C++ expressions representing the terms (in * order); the statement should be executed before the expressions are used. */ public Pair> gen(List ts, Map env) { List acc = new ArrayList<>(); List es = gen(acc, ts, env); return new Pair<>(CppSeq.mk(acc), es); } private class Worker { private final Map env; private final List acc; private final MatchCodeGen mcg = new MatchCodeGen(ctx); public Worker(List acc, Map env) { this.acc = acc; this.env = env; } public CppExpr go(Term t) { return t.accept(tv, null); } private final TermVisitor tv = new TermVisitor() { @Override public CppExpr visit(Var x, Void in) { assert env.containsKey(x); return env.get(x); } @Override public CppExpr visit(Constructor c, Void in) { ConstructorSymbol sym = c.getSymbol(); Term[] args = c.getArgs(); List cppArgs = new ArrayList<>(); for (Term arg : args) { cppArgs.add(arg.accept(this, in)); } CppExpr term = mkComplex(acc, sym, cppArgs); String tId = ctx.newId("t"); acc.add(CppDecl.mk(tId, term)); return CppVar.mk(tId); } @Override public CppExpr visit(Primitive p, Void in) { return CppBaseTerm.mk(p); } @Override public CppExpr visit(Expr e, Void in) { return e.accept(ev, in); } }; private final ExprVisitor ev = new ExprVisitor() { @Override public CppExpr visit(MatchExpr matchExpr, Void in) { Pair p = mcg.gen(matchExpr, env); acc.add(p.fst()); return p.snd(); } @Override public CppExpr visit(FunctionCall funcCall, Void in) { List args = new ArrayList<>(); for (Term arg : funcCall.getArgs()) { args.add(arg.accept(tv, in)); } return CppFuncCall.mk(ctx.lookupRepr(funcCall.getSymbol()), args); } @Override public CppExpr visit(LetFunExpr funcDefs, Void in) { throw new AssertionError("impossible"); } @Override public CppExpr visit(Fold fold, Void in) { List args = new ArrayList<>(); var f = ctx.lookupRepr(fold.getFunction()); args.add(CppExprFromString.mk(f)); for (Term arg : fold.getArgs()) { args.add(arg.accept(tv, in)); } return CppFuncCall.mk("funcs::fold", args); } }; } public static CppExpr genIntizeTerm(CppExpr term) { return CppMethodCall.mkThruPtr(term, "intize"); } public static CppExpr genUnintizeTerm(CppExpr id) { return CppFuncCall.mk("Term::unintize", id); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/TermCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Collections; import java.util.Set; public class TermCpp extends TemplateSrcFile { public TermCpp(CodeGenContext ctx) { super("Term.cpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker w = new Worker(out); CodeGenUtil.copyOver(br, out, 0); w.declareExplicitTemplateInstantiations(); CodeGenUtil.copyOver(br, out, 1); w.declareMakeGenericCases(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final Set symbols = ctx.getConstructorSymbols(); private final PrintWriter out; public Worker(PrintWriter out) { this.out = out; } void declareExplicitTemplateInstantiations() { for (ConstructorSymbol sym : symbols) { String args = String.join(", ", Collections.nCopies(sym.getArity(), "term_ptr")); out.print("template term_ptr Term::make<"); out.print(ctx.lookupRepr(sym)); if (sym.getArity() != 0) { out.print(", " + args); } out.println(">(" + args + ");"); } } void declareMakeGenericCases() { for (ConstructorSymbol sym : symbols) { String symName = ctx.lookupRepr(sym); int arity = sym.getArity(); String[] args = new String[arity]; for (int i = 0; i < arity; i++) args[i] = "terms[" + i + "]"; out.printf(" case %s:\n", symName); out.printf(" return Term::make<%s>(%s);\n", symName, String.join(", ", args)); } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/TypeCodeGen.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.*; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.OpaqueType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.types.Types.TypeVisitor; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class TypeCodeGen { private final CodeGenContext ctx; public TypeCodeGen(CodeGenContext ctx) { this.ctx = ctx; } /** * Take a Formulog type and generate its C++ representation. * * @param type The Formulog type that needs to be represented in C++ * @return A pair of a C++ statement and an C++ expression. The expression is the C++ * representation of the provided type; the statement is code that should be evaluated before * that expression is used. */ public Pair gen(Type type) { List acc = new ArrayList<>(); CppExpr e = gen(acc, type); return new Pair<>(CppSeq.mk(acc), e); } /** * Take a Formulog type and generate its C++ representation. Any C++ statements that need to be * run before the expression (representing that type) is used are appended to the given * accumulator list. * * @param acc An accumulator list of statements * @param type The Formulog type that needs to be represented in C++ * @return The C++ expression representing the given Formulog type */ public CppExpr gen(List acc, Type type) { return new Worker(acc, new HashMap<>()).go(type); } /** * Take a list of Formulog types and generate their C++ representation. Any C++ statements that * need to be run before the expressions (representing those types) are used are appended to the * given accumulator list. * * @param acc An accumulator list of statements * @param types The Formulog types that need to be represented in C++ * @return The C++ expressions representing the given Formulog types (and in the same order) */ public List gen(List acc, List types) { List es = new ArrayList<>(); for (Type ty : types) { gen(acc, ty); } return es; } /** * Take a list of Formulog types and generate their C++ representations. * * @param types The Formulog types that need to be represented in C++ * @return A pair of a C++ statement and a list of C++ expression. The expressions are the C++ * representations of the provided types (in the same order); the statement is code that * should be evaluated before those expressions are used. */ public Pair> gen(List types) { List acc = new ArrayList<>(); List es = gen(acc, types); return new Pair<>(CppSeq.mk(acc), es); } private class Worker { // Keep track of which type variables have already been generated private final Map env; private final List acc; public Worker(List acc, Map env) { this.acc = acc; this.env = env; } public CppExpr go(Type type) { if (type instanceof FunctorType) { return go1((FunctorType) type); } return type.accept(visitor, null); } private List go(List types) { List l = new ArrayList<>(); for (Type ty : types) { l.add(go(ty)); } return l; } private CppExpr go1(FunctorType type) { CppExpr ret = go(type.getRetType()); // A FunctorType is represented in C++ by a pair of the argument types and the // return type. return CppFuncCall.mk("make_pair", mkVec(type.getArgTypes()), ret); } TypeVisitor visitor = new TypeVisitor() { @Override public CppExpr visit(TypeVar typeVar, Void in) { CppExpr e = env.get(typeVar); if (e == null) { // Tell the C++ code to generate a fresh type variable String id = ctx.newId("ty"); acc.add(CppDecl.mk(id, CppFuncCall.mk("new_var"))); e = CppVar.mk(id); env.put(typeVar, e); } return e; } @Override public CppExpr visit(AlgebraicDataType algebraicType, Void in) { TypeSymbol sym = algebraicType.getSymbol(); CppExpr e = null; if (sym instanceof BuiltInTypeSymbol) { e = handleBuiltInType(algebraicType); } if (e != null) { return e; } // XXX Need to make sure this matches SMT declarations return mkType("|" + sym + "|", algebraicType.getTypeArgs()); } @Override public CppExpr visit(OpaqueType opaqueType, Void in) { throw new AssertionError("impossible"); } @Override public CppExpr visit(TypeIndex typeIndex, Void in) { return mkType(Integer.toString(typeIndex.getIndex())); } }; private CppExpr handleBuiltInType(AlgebraicDataType type) { BuiltInTypeSymbol sym = (BuiltInTypeSymbol) type.getSymbol(); List args = type.getTypeArgs(); switch (sym) { case ARRAY_TYPE: return mkType("Array", args); case BOOL_TYPE: return mkType("Bool", args); case BV: return mkType("_ BitVec", args); case STRING_TYPE: return mkType("String", args); case FP: return mkType("_ FloatingPoint", args); case INT_TYPE: return mkType("Int", args); // The rest of the built-in types can be treated as normal (i.e., user-defined) // types case LIST_TYPE: case OPTION_TYPE: case CMP_TYPE: case MODEL_TYPE: case SMT_PATTERN_TYPE: case SMT_TYPE: case SMT_WRAPPED_VAR_TYPE: case SYM_TYPE: case OPAQUE_SET: break; } return null; } private CppExpr mkType(String name) { return mkType(name, Collections.emptyList()); } private CppExpr mkType(String name, List args) { CppExpr cppName = CppConst.mkString(name); CppExpr vec = mkVec(args); String tyId = ctx.newId("ty"); // Create a new variable and initialize it. The initializer has the signature // (type_name, is_var, type_args). acc.add(CppCtor.mkInitializer("Type", tyId, cppName, CppConst.mkFalse(), vec)); return CppVar.mk(tyId); } private CppExpr mkVec(List args) { String vId = ctx.newId("v"); acc.add(CppCtor.mkInitializer("vector", vId, go(args))); return CppVar.mk(vId); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/TypeCpp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppExpr; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppReturn; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppSeq; import edu.harvard.seas.pl.formulog.codegen.ast.cpp.CppStmt; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; public class TypeCpp extends TemplateSrcFile { public TypeCpp(CodeGenContext ctx) { super("Type.cpp", ctx); } public void gen(BufferedReader br, PrintWriter out) throws IOException { Worker pr = new Worker(out); CodeGenUtil.copyOver(br, out, 0); pr.defineSymbolTypes(); CodeGenUtil.copyOver(br, out, -1); } private class Worker { private final PrintWriter out; private final TypeCodeGen tcg = new TypeCodeGen(ctx); public Worker(PrintWriter out) { this.out = out; } public void defineSymbolTypes() { for (ConstructorSymbol sym : ctx.getConstructorSymbols()) { defineSymbolType(sym); } } private void defineSymbolType(ConstructorSymbol sym) { out.println(" case " + ctx.lookupRepr(sym) + ": {"); genCaseBody(sym).println(out, 3); out.println(" }"); } private CppStmt genCaseBody(ConstructorSymbol sym) { List acc = new ArrayList<>(); FunctorType ft = simplify(sym.getCompileTimeType()); CppExpr typeCode = tcg.gen(acc, ft); acc.add(CppReturn.mk(typeCode)); return CppSeq.mk(acc); } private FunctorType simplify(FunctorType ft) { List args = new ArrayList<>(); for (Type ty : ft.getArgTypes()) { args.add(TypeChecker.simplify(ty)); } return new FunctorType(args, TypeChecker.simplify(ft.getRetType())); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppAccess.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppAccess implements CppExpr { private final CppExpr val; private final String field; private final boolean thruPtr; private CppAccess(CppExpr val, String field, boolean thruPtr) { this.val = val; this.field = field; this.thruPtr = thruPtr; } public static CppAccess mk(CppExpr val, String field) { return new CppAccess(val, field, false); } public static CppAccess mkThruPtr(CppExpr val, String field) { return new CppAccess(val, field, true); } @Override public void print(PrintWriter out) { val.print(out); out.print(thruPtr ? "->" : "."); out.print(field); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppBaseTerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.ast.BoolTerm; import edu.harvard.seas.pl.formulog.ast.FP32; import edu.harvard.seas.pl.formulog.ast.FP64; import edu.harvard.seas.pl.formulog.ast.I32; import edu.harvard.seas.pl.formulog.ast.I64; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.StringTerm; import java.io.PrintWriter; public class CppBaseTerm implements CppExpr { private final Primitive p; public static CppBaseTerm mk(Primitive p) { return new CppBaseTerm(p); } private CppBaseTerm(Primitive p) { this.p = p; } @Override public void print(PrintWriter out) { String type; String val = p.toString(); if (p instanceof I32) { type = "int32_t"; } else if (p instanceof I64) { type = "int64_t"; } else if (p instanceof FP32) { type = "float"; val = handleFloat(((FP32) p).getVal()); } else if (p instanceof FP64) { type = "double"; val = handleDouble(((FP64) p).getVal()); } else if (p instanceof BoolTerm) { type = "bool"; } else if (p instanceof StringTerm) { type = "string"; } else { throw new UnsupportedOperationException("Unsupported primitive: " + p); } printMakeTerm(type, val, out); } void printMakeTerm(String type, String val, PrintWriter out) { out.print("Term::make<"); out.print(type); out.print(">("); out.print(val); out.print(")"); } public static String handleDouble(double d) { if (d == Double.NEGATIVE_INFINITY) { return "-numeric_limits::infinity()"; } else if (d == Double.POSITIVE_INFINITY) { return "numeric_limits::infinity()"; } else if (Double.isNaN(d)) { return "nan(\"\")"; } else { return Double.toString(d); } } public static String handleFloat(float d) { if (d == Float.NEGATIVE_INFINITY) { return "-numeric_limits::infinity()"; } else if (d == Float.POSITIVE_INFINITY) { return "numeric_limits::infinity()"; } else if (Float.isNaN(d)) { return "nanf(\"\")"; } else { return Float.toString(d); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppBinop.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppBinop implements CppExpr { private final CppExpr lhs; private final String op; private final CppExpr rhs; private CppBinop(CppExpr lhs, String op, CppExpr rhs) { this.lhs = lhs; this.op = op; this.rhs = rhs; } private static CppBinop mk(CppExpr lhs, String op, CppExpr rhs) { return new CppBinop(lhs, op, rhs); } public static CppBinop mkOrUpdate(CppExpr lhs, CppExpr rhs) { return mk(lhs, "|=", rhs); } public static CppBinop mkLogAnd(CppExpr lhs, CppExpr rhs) { return mk(lhs, "&&", rhs); } public static CppBinop mkNotEq(CppExpr lhs, CppExpr rhs) { return mk(lhs, "!=", rhs); } public static CppBinop mkLt(CppExpr lhs, CppExpr rhs) { return mk(lhs, "<", rhs); } public static CppBinop mkAssign(CppExpr lhs, CppExpr rhs) { return mk(lhs, "=", rhs); } public static CppExpr mkEq(CppExpr lhs, CppExpr rhs) { return mk(lhs, "==", rhs); } public static CppExpr mkShiftLeft(CppExpr lhs, CppExpr rhs) { return mk(lhs, "<<", rhs); } @Override public void print(PrintWriter out) { out.print("("); lhs.print(out); out.print(" "); out.print(op); out.print(" "); rhs.print(out); out.print(")"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppBlock.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppBlock implements CppStmt { private final CppStmt stmt; private CppBlock(CppStmt stmt) { this.stmt = stmt; } public static CppBlock mk(CppStmt stmt) { return new CppBlock(stmt); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.println("{"); stmt.println(out, indent + 1); CodeGenUtil.printIndent(out, indent); out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppCast.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppCast implements CppExpr { private final String type; private final CppExpr expr; private final String castName; private CppCast(String castName, String type, CppExpr expr) { this.type = type; this.expr = expr; this.castName = castName; } public static CppCast mkReinterpret(String type, CppExpr expr) { return new CppCast("reinterpret_cast", type, expr); } @Override public void print(PrintWriter out) { out.print(castName); out.print("<"); out.print(type); out.print(">"); out.print("("); expr.print(out); out.print(")"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppConst.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppConst implements CppExpr { private final T val; private CppConst(T val) { this.val = val; } public static CppConst mkTrue() { return new CppConst<>(true); } public static CppConst mkFalse() { return new CppConst<>(false); } public static CppConst mkInt(int i) { return new CppConst<>(i); } public static CppConst mkString(String s) { return new CppConst<>(s); } @Override public void print(PrintWriter out) { boolean isString = val instanceof String; if (isString) { out.print("\""); } out.print(val); if (isString) { out.print("\""); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppCtor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; public class CppCtor implements CppStmt { private final String type; private final String var; private final List args; private final boolean initializer; private CppCtor(String type, String var, List args, boolean initializer) { this.type = type; this.var = var; this.args = args; this.initializer = initializer; } public static CppCtor mk(String type, String var, List args) { return new CppCtor(type, var, args, false); } public static CppCtor mk(String type, String var, CppExpr... args) { return new CppCtor(type, var, Arrays.asList(args), false); } public static CppCtor mkInitializer(String type, String var, List args) { return new CppCtor(type, var, args, true); } public static CppCtor mkInitializer(String type, String var, CppExpr... args) { return new CppCtor(type, var, Arrays.asList(args), true); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print(type); out.print(" "); out.print(var); if (!args.isEmpty()) { out.print(initializer ? "{" : "("); CodeGenUtil.printSeparated(args, ", ", out); out.print(initializer ? "}" : ")"); } out.println(";"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppDecl.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppDecl implements CppStmt { private final String type; private final String var; private final CppExpr val; private CppDecl(String type, String var, CppExpr val) { this.type = type; this.var = var; this.val = val; } public static CppDecl mk(String var, CppExpr val) { return new CppDecl("auto", var, val); } public static CppDecl mkRef(String var, CppExpr val) { return new CppDecl("auto&", var, val); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print(type); out.print(" "); out.print(var); out.print(" = "); val.print(out); out.println(";"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppExpr.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public interface CppExpr { void print(PrintWriter out); default CppStmt toStmt() { return new CppStmt() { @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); CppExpr.this.print(out); out.println(";"); } }; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppExprFromString.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppExprFromString implements CppExpr { private final String s; private CppExprFromString(String s) { this.s = s; } public static CppExprFromString mk(String s) { return new CppExprFromString(s); } @Override public void print(PrintWriter out) { out.print(s); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppFor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppFor implements CppStmt { private final String var; private final CppExpr init; private final CppExpr guard; private final CppExpr update; private final CppStmt body; private final boolean parallel; private CppFor( String var, CppExpr init, CppExpr guard, CppExpr update, CppStmt body, boolean parallel) { this.var = var; this.init = init; this.guard = guard; this.update = update; this.body = body; this.parallel = parallel; } public static CppFor mk(String var, CppExpr init, CppExpr guard, CppExpr update, CppStmt body) { return new CppFor(var, init, guard, update, body, false); } public static CppFor mkParallel( String var, CppExpr init, CppExpr guard, CppExpr update, CppStmt body) { return new CppFor(var, init, guard, update, body, true); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print(parallel ? "pfor" : "for"); out.print(" (auto "); out.print(var); out.print(" = "); init.print(out); out.print("; "); guard.print(out); out.print("; "); update.print(out); out.println(") {"); body.println(out, indent + 1); CodeGenUtil.printIndent(out, indent); out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppForEach.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppForEach implements CppStmt { private final String var; private final CppExpr val; private final CppStmt body; private CppForEach(String var, CppExpr val, CppStmt body) { this.var = var; this.val = val; this.body = body; } public static CppForEach mk(String var, CppExpr val, CppStmt body) { return new CppForEach(var, val, body); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print("for (const auto& "); out.print(var); out.print(" : "); val.print(out); out.println(") {"); body.println(out, indent + 1); CodeGenUtil.printIndent(out, indent); out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppFuncCall.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; public class CppFuncCall implements CppExpr { private final String func; private final List args; private CppFuncCall(String func, List args) { this.func = func; this.args = args; } public static CppFuncCall mk(String func, List args) { return new CppFuncCall(func, args); } public static CppFuncCall mk(String func, CppExpr... args) { return mk(func, Arrays.asList(args)); } @Override public void print(PrintWriter out) { out.print(func); out.print("("); CodeGenUtil.printSeparated(args, ", ", out); out.print(")"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppGoto.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppGoto implements CppStmt { private final String label; private CppGoto(String label) { this.label = label; } public static CppGoto mk(String label) { return new CppGoto(label); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.println("goto " + label + ";"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppIf.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class CppIf implements CppStmt { private final List> cases; private final CppStmt elseBranch; private CppIf(List> cases, CppStmt elseBranch) { if (cases.isEmpty()) { throw new IllegalArgumentException("Need to have at least one case in an if statement"); } this.cases = cases; this.elseBranch = elseBranch; } public static CppIf mk(CppExpr guard, CppStmt thenBranch) { return mk(guard, thenBranch, null); } public static CppIf mk(CppExpr guard, CppStmt thenBranch, CppStmt elseBranch) { return new CppIf(Collections.singletonList(new Pair<>(guard, thenBranch)), elseBranch); } public static CppIf mk(List> cases) { return new CppIf(new ArrayList<>(cases), null); } @Override public void println(PrintWriter out, int indent) { boolean first = true; for (Pair p : cases) { CppExpr guard = p.fst(); CppStmt code = p.snd(); CodeGenUtil.printIndent(out, indent); if (!first) { out.print("} else "); } out.print("if ("); guard.print(out); out.println(") {"); code.println(out, indent + 1); first = false; } CodeGenUtil.printIndent(out, indent); if (elseBranch == null) { out.println("}"); return; } out.println("} else {"); elseBranch.println(out, indent + 1); CodeGenUtil.printIndent(out, indent); out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppLabel.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppLabel implements CppStmt { private final String label; public CppLabel(String label) { this.label = label; } public static CppLabel mk(String label) { return new CppLabel(label); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.println(label + ":"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppMethodCall.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; public class CppMethodCall implements CppExpr { private final String func; private final CppExpr rec; private final List args; private final boolean thruPtr; private CppMethodCall(CppExpr rec, String func, List args, boolean thruPtr) { this.func = func; this.rec = rec; this.args = args; this.thruPtr = thruPtr; } public static CppMethodCall mk(CppExpr rec, String func, List args) { return new CppMethodCall(rec, func, args, false); } public static CppMethodCall mk(CppExpr rec, String func, CppExpr... args) { return new CppMethodCall(rec, func, Arrays.asList(args), false); } public static CppMethodCall mkThruPtr(CppExpr rec, String func, List args) { return new CppMethodCall(rec, func, args, true); } public static CppMethodCall mkThruPtr(CppExpr rec, String func, CppExpr... args) { return new CppMethodCall(rec, func, Arrays.asList(args), true); } @Override public void print(PrintWriter out) { rec.print(out); out.print(thruPtr ? "->" : "."); out.print(func); out.print("("); CodeGenUtil.printSeparated(args, ", ", out); out.print(")"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppNewArray.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppNewArray implements CppExpr { private final String type; private final CppExpr size; private CppNewArray(String type, CppExpr size) { this.type = type; this.size = size; } public static CppNewArray mk(String type, CppExpr size) { return new CppNewArray(type, size); } @Override public void print(PrintWriter out) { out.print("new "); out.print(type); out.print("["); size.print(out); out.print("]"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppNullptr.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public enum CppNullptr implements CppExpr { INSTANCE; @Override public void print(PrintWriter out) { out.print("nullptr"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppReturn.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppReturn implements CppStmt { private final CppExpr val; private CppReturn(CppExpr val) { this.val = val; } public static CppReturn mk(CppExpr val) { return new CppReturn(val); } public static CppReturn mk() { return new CppReturn(null); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); if (val == null) { out.println("return;"); return; } out.print("return "); val.print(out); out.println(";"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppSeq.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.List; public class CppSeq implements CppStmt { private final List stmts; private CppSeq(List stmts) { this.stmts = stmts; } public static CppSeq mk(List stmts) { if (stmts.isEmpty()) { return skip; } return new CppSeq(stmts); } public static CppSeq mk(CppStmt... stmts) { return mk(Arrays.asList(stmts)); } public static CppSeq skip() { return skip; } private static final CppSeq skip = new CppSeq(Collections.emptyList()); @Override public void println(PrintWriter out, int indent) { CodeGenUtil.print(stmts, out, indent); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppStmt.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public interface CppStmt { void println(PrintWriter out, int indent); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppSubscript.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppSubscript implements CppExpr { private final CppExpr val; private final CppExpr idx; private CppSubscript(CppExpr val, CppExpr idx) { this.val = val; this.idx = idx; } public static CppSubscript mk(CppExpr val, CppExpr idx) { return new CppSubscript(val, idx); } @Override public void print(PrintWriter out) { val.print(out); out.print("["); idx.print(out); out.print("]"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppUnop.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppUnop implements CppExpr { private final String op; private final CppExpr expr; private CppUnop(String op, CppExpr expr) { this.op = op; this.expr = expr; } private static CppUnop mk(String op, CppExpr expr) { return new CppUnop(op, expr); } public static CppUnop mkNot(CppExpr expr) { return mk("!", expr); } public static CppUnop mkPreIncr(CppExpr expr) { return mk("++", expr); } public static CppUnop mkDeref(CppExpr expr) { return mk("*", expr); } @Override public void print(PrintWriter out) { out.print("("); out.print(op); expr.print(out); out.print(")"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppVar.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; public class CppVar implements CppExpr { private final String var; private CppVar(String var) { this.var = var; } public static CppVar mk(String var) { return new CppVar(var); } @Override public String toString() { return var; } @Override public void print(PrintWriter out) { out.print(var); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppVectorLiteral.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import java.io.PrintWriter; import java.util.List; public class CppVectorLiteral implements CppExpr { private final List elts; private CppVectorLiteral(List elts) { this.elts = elts; } public static CppVectorLiteral mk(List elts) { return new CppVectorLiteral(elts); } @Override public void print(PrintWriter out) { out.print("{"); for (int i = 0; i < elts.size(); ++i) { elts.get(i).print(out); if (i < elts.size() - 1) { out.print(", "); } } out.print("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/cpp/CppWhile.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.cpp; import edu.harvard.seas.pl.formulog.codegen.CodeGenUtil; import java.io.PrintWriter; public class CppWhile implements CppStmt { private final CppExpr guard; private final CppStmt body; private CppWhile(CppExpr guard, CppStmt body) { this.guard = guard; this.body = body; } public static CppWhile mk(CppExpr guard, CppStmt body) { return new CppWhile(guard, body); } @Override public void println(PrintWriter out, int indent) { CodeGenUtil.printIndent(out, indent); out.print("while ("); guard.print(out); out.println(") {"); body.println(out, indent + 1); CodeGenUtil.printIndent(out, indent); out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SAtom.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.List; import java.util.Set; public class SAtom implements SLit { private final String pred; private final List args; private final boolean isNegated; public SAtom(String symbol, List args, boolean isNegated) { pred = symbol; this.args = args; this.isNegated = isNegated; } public String getSymbol() { return pred; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isNegated) { sb.append("!"); } sb.append(pred); sb.append("("); for (int i = 0; i < args.size(); ++i) { sb.append(args.get(i)); if (i < args.size() - 1) { sb.append(", "); } } sb.append(")"); return sb.toString(); } @Override public void varSet(Set vars) { for (var t : args) { t.varSet(vars); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SDestructorBody.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import java.util.List; public class SDestructorBody implements SFunctorBody { private final List args; private final Term scrutinee; private final ConstructorSymbol symbol; public SDestructorBody(List args_, Term scrutinee_, ConstructorSymbol symbol_) { args = args_; scrutinee = scrutinee_; symbol = symbol_; } @Override public List getArgs() { return args; } @Override public SType getRetType() { return SIntType.INSTANCE; } @Override public boolean isStateful() { return false; } public Term getScrutinee() { return scrutinee; } public ConstructorSymbol getSymbol() { return symbol; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SExprBody.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.List; public class SExprBody implements SFunctorBody { private final List args; private final Term body; public SExprBody(List args_, Term body_) { args = args_; body = body_; } public Term getBody() { return body; } @Override public List getArgs() { return args; } @Override public SType getRetType() { return SIntType.INSTANCE; } @Override public boolean isStateful() { return false; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SFunctorBody.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.List; public interface SFunctorBody { default int getArity() { return getArgs().size(); } List getArgs(); SType getRetType(); boolean isStateful(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SFunctorCall.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.Arrays; import java.util.List; import java.util.Set; public class SFunctorCall implements STerm { private final String func; private final List args; public SFunctorCall(String func, List args) { this.func = func; this.args = args; } public SFunctorCall(String func, STerm... args) { this(func, Arrays.asList(args)); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("@"); sb.append(func); sb.append("("); for (int i = 0; i < args.size(); ++i) { sb.append(args.get(i)); if (i < args.size() - 1) { sb.append(", "); } } sb.append(")"); return sb.toString(); } @Override public void varSet(Set vars) { for (var t : args) { t.varSet(vars); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SInfixBinaryOpAtom.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.Set; public class SInfixBinaryOpAtom implements SLit { private final STerm lhs; private final String op; private final STerm rhs; public SInfixBinaryOpAtom(STerm lhs_, String op_, STerm rhs_) { lhs = lhs_; op = op_; rhs = rhs_; } @Override public String toString() { return lhs + " " + op + " " + rhs; } @Override public void varSet(Set vars) { lhs.varSet(vars); rhs.varSet(vars); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SInt.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.Set; public class SInt implements STerm { private final int val; public SInt(int val_) { val = val_; } @Override public String toString() { return Integer.toString(val); } @Override public void varSet(Set vars) { // do nothing } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SIntList.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.List; import java.util.Set; public class SIntList implements STerm { private final List ts; public SIntList(List ts_) { ts = ts_; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i < ts.size(); ++i) { sb.append(ts.get(i)); if (i < ts.size() - 1) { sb.append(", "); } } sb.append("]"); return sb.toString(); } @Override public void varSet(Set vars) { for (var t : ts) { t.varSet(vars); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SIntListType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.Objects; public class SIntListType implements SType { private final int arity; public SIntListType(int arity) { this.arity = arity; } public String getName() { return "IntList" + arity; } public String getDef() { StringBuilder sb = new StringBuilder(); sb.append("["); for (int i = 0; i < arity; ++i) { sb.append("x"); sb.append(i); sb.append(":number"); if (i < arity - 1) { sb.append(", "); } } sb.append("]"); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SIntListType that = (SIntListType) o; return arity == that.arity; } @Override public int hashCode() { return Objects.hash(arity); } @Override public String toString() { return getName(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SIntType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; public enum SIntType implements SType { INSTANCE; @Override public String toString() { return "number"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SLit.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.HashSet; import java.util.Set; public interface SLit { default Set varSet() { Set vars = new HashSet<>(); varSet(vars); return vars; } void varSet(Set vars); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Arrays; import java.util.Collections; import java.util.List; public class SRule { private final SLit head; private final List body; private List> queryPlan; public SRule(SLit head, List body) { this.head = head; this.body = body; } public SRule(SLit head, SLit... body) { this(head, Arrays.asList(body)); } public List getBody() { return Collections.unmodifiableList(body); } public void setQueryPlan(List> queryPlan) { this.queryPlan = queryPlan; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(head); if (!body.isEmpty()) { sb.append(" :- "); String sep = ""; if (body.size() > 1) { sep = "\n\t"; } for (int i = 0; i < body.size(); ++i) { sb.append(sep); sb.append(body.get(i)); if (i < body.size() - 1) { sb.append(", "); } } } sb.append("."); if (queryPlan != null) { sb.append("\n"); for (int i = 0; i < queryPlan.size(); ++i) { if (i == 0) { sb.append(".plan "); } else { sb.append(", "); } var p = queryPlan.get(i); sb.append(p.fst()); sb.append(": ("); var plan = p.snd(); for (int j = 0; j < plan.length; ++j) { sb.append(plan[j]); if (j < plan.length - 1) { sb.append(","); } } sb.append(")"); } } return sb.toString(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SRuleMode.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; public enum SRuleMode { INPUT, OUTPUT, INTERMEDIATE; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/STerm.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import java.util.HashSet; import java.util.Set; public interface STerm { default Set varSet() { Set vars = new HashSet<>(); varSet(vars); return vars; } void varSet(Set vars); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; public interface SType {} ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/codegen/ast/souffle/SVar.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2022-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen.ast.souffle; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.Set; public class SVar implements STerm { private final String name; public SVar(Var var) { String s = var.toString(); if (s.charAt(0) == '$') { s = "x" + s.substring(1); } name = s; } @Override public String toString() { return name; } @Override public void varSet(Set vars) { vars.add(this); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SVar other = (SVar) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/BindingTypeArrayWrapper.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.ast.BindingType; import java.util.Arrays; public class BindingTypeArrayWrapper { private final BindingType[] arr; public BindingTypeArrayWrapper(BindingType[] arr) { this.arr = arr; } public BindingType[] getArr() { return arr; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(arr); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BindingTypeArrayWrapper other = (BindingTypeArrayWrapper) obj; if (!Arrays.equals(arr, other.arr)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/ExampleComparator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.ast.Term; import java.util.Comparator; public class ExampleComparator implements Comparator { @Override public int compare(Term[] xs, Term[] ys) { int xid = xs[0].getId(); int yid = ys[0].getId(); if (xid < yid) { return -1; } else if (xid > yid) { return 1; } xid = xs[2].getId(); yid = ys[2].getId(); if (xid < yid) { return -1; } else if (xid > yid) { return 1; } xid = xs[1].getId(); yid = ys[1].getId(); if (xid < yid) { return -1; } else if (xid > yid) { return 1; } return 0; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/IndexedFactDb.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import java.util.Set; public interface IndexedFactDb { Set getSymbols(); Iterable getAll(RelationSymbol sym); boolean isEmpty(RelationSymbol sym); int countDistinct(RelationSymbol sym); int numIndices(RelationSymbol sym); int countDuplicates(RelationSymbol sym); Iterable get(RelationSymbol sym, Term[] key, int index); boolean add(RelationSymbol sym, Term[] args); boolean addAll(RelationSymbol sym, Iterable tups); boolean hasFact(RelationSymbol sym, Term[] args); void clear(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/IndexedFactDbBuilder.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; public interface IndexedFactDbBuilder { int makeIndex(RelationSymbol sym, BindingType[] pat); T build(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/MinChainCover.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import org.jgrapht.Graph; import org.jgrapht.alg.flow.PushRelabelMFImpl; import org.jgrapht.alg.interfaces.MaximumFlowAlgorithm; import org.jgrapht.graph.SimpleDirectedGraph; public class MinChainCover { private final BiFunction lessThan; public MinChainCover(BiFunction lessThan) { this.lessThan = lessThan; } public Iterable> compute(Set elts) { return new Worker(elts).go(); } private class Worker { private final Graph bigraph = new SimpleDirectedGraph<>(Edge.class); private final T[] elts; private final Boolean[][] memo; private final Node source = new Node() { @Override public String toString() { return "source"; } }; private final Node sink = new Node() { @Override public String toString() { return "sink"; } }; @SuppressWarnings("unchecked") public Worker(Set s) { int n = s.size(); elts = (T[]) new Object[n]; int i = 0; for (T elt : s) { elts[i] = elt; ++i; } memo = new Boolean[n][n]; } public Iterable> go() { mkBipartiteGraph(); return mkChains(); } private void mkBipartiteGraph() { bigraph.addVertex(source); bigraph.addVertex(sink); for (T elt : elts) { Node left = new InnerNode(elt, false); Node right = new InnerNode(elt, true); bigraph.addVertex(left); bigraph.addVertex(right); bigraph.addEdge(source, left, new Edge(source, left)); bigraph.addEdge(right, sink, new Edge(right, sink)); } for (int i = 0; i < elts.length; ++i) { Node iv = new InnerNode(elts[i], false); for (int j = 0; j < elts.length; ++j) { considerEdge(i, iv, j); } } } private void considerEdge(int i, Node iv, int j) { if (memo[i][j] == null) { memoize(i, j); } if (memo[i][j]) { Node jv = new InnerNode(elts[j], true); bigraph.addEdge(iv, jv, new Edge(iv, jv)); } } private void memoize(int i, int j) { if (i == j) { memo[i][j] = false; return; } if (lessThan.apply(elts[i], elts[j])) { memo[i][j] = true; for (int k = 0; k < elts.length; ++k) { Boolean b = memo[j][k]; if (b != null && b) { memo[i][k] = true; } } } else { memo[i][j] = false; } } private Map computeMaxFlowMap() { MaximumFlowAlgorithm maxFlow = new PushRelabelMFImpl<>(bigraph); return maxFlow.getMaximumFlow(source, sink).getFlowMap(); } @SuppressWarnings("unchecked") private Iterable> mkChains() { Map maxFlowMap = computeMaxFlowMap(); Set roots = new HashSet<>(Arrays.asList(elts)); Map m = new HashMap<>(); for (Map.Entry entry : maxFlowMap.entrySet()) { if (entry.getValue() > 0) { Edge edge = entry.getKey(); if (edge.src == source || edge.dst == sink) { continue; } InnerNode src = (InnerNode) edge.src; InnerNode dst = (InnerNode) edge.dst; roots.remove(dst.elt); T other = m.put(src.elt, dst.elt); assert other == null; } } List> chains = new ArrayList<>(); for (T root : roots) { T cur = root; List chain = new ArrayList<>(); while (cur != null) { chain.add(cur); cur = m.get(cur); } chains.add(chain); } return chains; } } private static interface Node {} private class InnerNode implements Node { public final T elt; public final boolean side; public InnerNode(T elt, boolean side) { this.elt = elt; this.side = side; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((elt == null) ? 0 : elt.hashCode()); result = prime * result + (side ? 1231 : 1237); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("unchecked") InnerNode other = (InnerNode) obj; if (elt == null) { if (other.elt != null) return false; } else if (!elt.equals(other.elt)) return false; if (side != other.side) return false; return true; } @Override public String toString() { return elt + ((side) ? "@R" : "@L"); } } private static class Edge { public final Node src; public final Node dst; public Edge(Node src, Node dst) { this.src = src; this.dst = dst; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dst == null) ? 0 : dst.hashCode()); result = prime * result + ((src == null) ? 0 : src.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Edge other = (Edge) obj; if (dst == null) { if (other.dst != null) return false; } else if (!dst.equals(other.dst)) return false; if (src == null) { if (other.src != null) return false; } else if (!src.equals(other.src)) return false; return true; } @Override public String toString() { return src + " ---> " + dst; } } public static void main(String[] args) { Set s1 = new HashSet<>(Arrays.asList("x")); Set s2 = new HashSet<>(Arrays.asList("x", "y")); Set s3 = new HashSet<>(Arrays.asList("x", "z")); Set s4 = new HashSet<>(Arrays.asList("x", "y", "z")); Set> elts = new HashSet<>(Arrays.asList(s1, s2, s3, s4)); MinChainCover> mcc = new MinChainCover<>((x, y) -> y.containsAll(x)); System.out.println(mcc.compute(elts)); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/MinIndex.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public final class MinIndex { private MinIndex() { throw new AssertionError("impossible"); } public static Map, Iterable> compute(Set domain, Set> searches) { MinChainCover> mcc = new MinChainCover<>((x, y) -> y.containsAll(x)); Iterable>> chains = mcc.compute(searches); Map, Iterable> idxs = new HashMap<>(); for (Iterable> chain : chains) { List idx = computeIndex(chain); padIndex(idx, domain); for (Set search : chain) { Iterable other = idxs.put(search, idx); assert other == null; } } return idxs; } private static void padIndex(List idx, Set domain) { Set seen = new HashSet<>(idx); for (T elt : domain) { if (seen.add(elt)) { idx.add(elt); } } } private static List computeIndex(Iterable> chain) { List index = new ArrayList<>(); Set prev = Collections.emptySet(); for (Set s : chain) { for (T elt : s) { if (!prev.contains(elt)) { index.add(elt); } } prev = s; } return index; } public static void main(String[] args) { Set s1 = new HashSet<>(Arrays.asList("x")); Set s2 = new HashSet<>(Arrays.asList("x", "y")); Set s3 = new HashSet<>(Arrays.asList("x", "z")); Set s4 = new HashSet<>(Arrays.asList("x", "y", "z")); Set> searches = new HashSet<>(Arrays.asList(s1, s2, s3, s4)); System.out.println(MinIndex.compute(s4, searches)); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/SortedIndexedFactDb.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.SymbolComparator; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableSet; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class SortedIndexedFactDb implements IndexedFactDb { private final Map[]> indices; private final Map> masterIndex; private final Map> hashFilter = new HashMap<>(); private final ThreadLocal hashKey = ThreadLocal.withInitial(HashedTuple::new); private SortedIndexedFactDb( Map[]> indices, Map> masterIndex) { this.indices = indices; this.masterIndex = masterIndex; for (var sym : indices.keySet()) { hashFilter.put(sym, Util.concurrentSet()); } } private static class HashedTuple { public Term[] tup; @Override public int hashCode() { return Arrays.hashCode(tup); } @Override public boolean equals(Object other) { if (other instanceof HashedTuple) { return Arrays.equals(tup, ((HashedTuple) other).tup); } return false; } } @Override public Set getSymbols() { return Collections.unmodifiableSet(masterIndex.keySet()); } @Override public Iterable getAll(RelationSymbol sym) { return masterIndex.get(sym).fst().getAll(); } @Override public boolean isEmpty(RelationSymbol sym) { return masterIndex.get(sym).fst().isEmpty(); } @Override public int countDistinct(RelationSymbol sym) { return masterIndex.get(sym).fst().count(); } @Override public int countDuplicates(RelationSymbol sym) { int count = 0; for (IndexedFactSet idx : getUniqueIndices(sym)) { count += idx.count(); } return count; } private Set getUniqueIndices(RelationSymbol sym) { Set s = new HashSet<>(); for (Pair e : indices.get(sym)) { s.add(e.fst()); } return s; } @Override public Iterable get(RelationSymbol sym, Term[] key, int index) { Pair p = indices.get(sym)[index]; return p.fst().lookup(key, p.snd()); } private boolean hashFilterAdd(RelationSymbol sym, Term[] tup) { var key = hashKey.get(); key.tup = tup; if (hashFilter.get(sym).add(key)) { hashKey.set(new HashedTuple()); return true; } key.tup = null; return false; } private boolean hashFilterContains(RelationSymbol sym, Term[] tup) { var key = hashKey.get(); key.tup = tup; var r = hashFilter.get(sym).contains(key); key.tup = null; return r; } @Override public boolean add(RelationSymbol sym, Term[] tup) { assert allNormal(tup); if (Configuration.useHashDbFilter && !hashFilterAdd(sym, tup)) { return false; } IndexedFactSet master = masterIndex.get(sym).fst(); if (master.add(tup)) { for (Pair p : indices.get(sym)) { IndexedFactSet idx = p.fst(); if (!idx.equals(master)) { idx.add(tup); } } return true; } return false; } @Override public boolean addAll(RelationSymbol sym, Iterable tups) { if (Configuration.useHashDbFilter) { ArrayList l = new ArrayList<>(); for (var tup : tups) { if (hashFilterAdd(sym, tup)) { l.add(tup); } } tups = l; } IndexedFactSet master = masterIndex.get(sym).fst(); if (master.addAll(tups)) { for (Pair p : indices.get(sym)) { IndexedFactSet idx = p.fst(); if (!idx.equals(master)) { idx.addAll(tups); } } return true; } return false; } private boolean allNormal(Term[] args) { for (Term arg : args) { if (!arg.isGround() || arg.containsUnevaluatedTerm()) { return false; } } return true; } @Override public boolean hasFact(RelationSymbol sym, Term[] args) { assert allNormal(args); if (Configuration.useHashDbFilter) { return hashFilterContains(sym, args); } return masterIndex.get(sym).fst().contains(args); } @Override public void clear() { for (var s : hashFilter.values()) { s.clear(); } for (Pair[] idxs : indices.values()) { for (Pair p : idxs) { p.fst().clear(); } } } @Override public String toString() { String s = "{\n"; for (RelationSymbol sym : masterIndex.keySet()) { s += "\t" + sym + " = {\n"; for (Pair p : indices.get(sym)) { s += "\t" + Arrays.toString(p.snd()) + "\n"; s += p.fst().toString() + "\n"; } s += "\t}\n"; } return s + "}"; } public String toSimplifiedString() { String s = "{\n"; for (RelationSymbol sym : masterIndex.keySet()) { Pair p = masterIndex.get(sym); IndexedFactSet idx = p.fst(); if (!idx.isEmpty()) { s += "\t" + sym + " = {\n"; s += "\t" + Arrays.toString(p.snd()) + "\n"; s += idx.toString() + "\n"; s += "\t}\n"; } } return s + "}"; } @Override public int numIndices(RelationSymbol sym) { if (!indices.containsKey(sym)) { throw new IllegalArgumentException("Unrecognized symbol: " + sym); } return getUniqueIndices(sym).size(); } public IndexInfo getIndexInfo(RelationSymbol sym, int idx) { if (idx < 0 || idx > numIndices(sym)) { throw new IllegalArgumentException("Unrecognized index for symbol " + sym + ": " + idx); } Pair p = indices.get(sym)[idx]; IndexedFactSet index = p.fst(); return new IndexInfo(index.getId(), index.comparatorOrder, p.snd()); } public int getMasterIndex(RelationSymbol sym) { if (!indices.containsKey(sym)) { throw new IllegalArgumentException("Unrecognized symbol: " + sym); } int i = 0; IndexedFactSet master = masterIndex.get(sym).fst(); for (Pair p : indices.get(sym)) { if (p.fst().equals(master)) { break; } i++; } return i; } public static class SortedIndexedFactDbBuilder implements IndexedFactDbBuilder { private final Map counts = new HashMap<>(); private final Map> pats = new LinkedHashMap<>(); private final Map> masterIndex = new HashMap<>(); public SortedIndexedFactDbBuilder(Set allSyms) { List sortedSyms = allSyms.stream().sorted(SymbolComparator.INSTANCE).collect(Collectors.toList()); for (RelationSymbol sym : sortedSyms) { pats.put(sym, new HashMap<>()); counts.put(sym, 0); } } @Override public synchronized int makeIndex(RelationSymbol sym, BindingType[] pat) { assert sym.getArity() == pat.length; Map m = pats.get(sym); BindingTypeArrayWrapper key = new BindingTypeArrayWrapper(pat); assert m != null : "Symbol not registered with DB: " + sym; Integer idx = m.get(key); if (idx == null) { idx = counts.get(sym); counts.put(sym, idx + 1); m.put(key, idx); } return idx; } @Override public SortedIndexedFactDb build() { Map[]> indices = new HashMap<>(); for (Map.Entry> e : pats.entrySet()) { RelationSymbol sym = e.getKey(); indices.put(sym, mkIndices(sym, e.getValue())); } List sortedSyms = counts.keySet().stream().sorted(SymbolComparator.INSTANCE).collect(Collectors.toList()); HashMap> sorted = new LinkedHashMap<>(); for (RelationSymbol sym : sortedSyms) { sorted.put(sym, masterIndex.get(sym)); } return new SortedIndexedFactDb(indices, sorted); } @SuppressWarnings("unchecked") private Pair[] mkIndices( RelationSymbol sym, Map m) { List> indices; if (Configuration.minIndex) { indices = mkMinIndices(sym, m); } else { indices = mkNaiveIndices(sym, m); } boolean ok = false; for (Pair p : indices) { IndexedFactSet idx = p.fst(); if (idx.comparatorLength() == sym.getArity()) { masterIndex.put(sym, new Pair<>(idx, p.snd())); ok = true; break; } } if (!ok) { BindingType[] pat = new BindingType[sym.getArity()]; for (int i = 0; i < pat.length; ++i) { pat[i] = BindingType.FREE; } IndexedFactSet master = IndexedFactSet.make(pat); Pair p = new Pair<>(master, pat); masterIndex.put(sym, p); indices.add(p); } assert indicesWellFormed(indices) : "Bad index created for relation: " + sym; return indices.toArray(new Pair[0]); } private static boolean indicesWellFormed( Iterable> indices) { boolean ok = true; for (Pair p : indices) { ok &= p.fst().comparatorLength() == countNumNotIgnored(p.snd()); } return ok; } private static int countNumNotIgnored(BindingType[] pat) { int i = 0; for (BindingType b : pat) { if (!b.isIgnored()) { i++; } } return i; } private List> mkNaiveIndices( RelationSymbol sym, Map m) { List> idxs = new ArrayList<>(); List> sorted = m.entrySet().stream().sorted(cmp).collect(Collectors.toList()); for (Map.Entry e : sorted) { BindingType[] pat = e.getKey().getArr(); IndexedFactSet idx = IndexedFactSet.make(pat); idxs.add(new Pair<>(idx, pat)); } return idxs; } private List> mkMinIndices( RelationSymbol sym, Map m) { List> indices = new ArrayList<>(m.size()); for (int i = 0; i < m.size(); ++i) { indices.add(null); } for (Map.Entry, Set>> e1 : partitionByIgnoredPositions(m).entrySet()) { for (Map.Entry> e2 : mkMinIndices(sym, e1.getKey(), e1.getValue()).entrySet()) { assert indices.get(e2.getKey()) == null; indices.set(e2.getKey(), e2.getValue()); } } return indices; } private Map> mkMinIndices( RelationSymbol sym, Set ignored, Set> s) { Map> searchByNum = new HashMap<>(); Map bindingsByNum = new HashMap<>(); Set> searches = new HashSet<>(); for (Pair p : s) { Set search = new HashSet<>(); BindingType[] pat = p.snd(); for (int i = 0; i < pat.length; ++i) { if (pat[i].isBound()) { search.add(i); } } searches.add(search); int i = p.fst(); searchByNum.put(i, search); bindingsByNum.put(i, pat); } Set domain = mkDomain(sym.getArity(), ignored); Map, Iterable> indexBySearch = MinIndex.compute(domain, searches); Map, IndexedFactSet> factSetByIndex = new HashMap<>(); for (Iterable idx : indexBySearch.values()) { List order = new ArrayList<>(); for (int i : idx) { order.add(i); } factSetByIndex.put(idx, IndexedFactSet.make(order)); } Map> indices = new HashMap<>(); for (int i : searchByNum.keySet()) { Set search = searchByNum.get(i); BindingType[] pat = bindingsByNum.get(i); Iterable idx = indexBySearch.get(search); indices.put(i, new Pair<>(factSetByIndex.get(idx), pat)); } return indices; } private static Set mkDomain(int arity, Set ignored) { Set s = new HashSet<>(); for (int i = 0; i < arity; ++i) { if (!ignored.contains(i)) { s.add(i); } } return s; } private static Map, Set>> partitionByIgnoredPositions( Map m) { Map, Set>> byIgnoredPos = new HashMap<>(); for (Map.Entry e : m.entrySet()) { BindingType[] pat = e.getKey().getArr(); Set ignored = findIgnoredPositions(pat); Pair p = new Pair<>(e.getValue(), pat); Util.lookupOrCreate(byIgnoredPos, ignored, () -> new HashSet<>()).add(p); } return byIgnoredPos; } private static Set findIgnoredPositions(BindingType[] pat) { Set ignored = new HashSet<>(); for (int i = 0; i < pat.length; ++i) { if (pat[i].isIgnored()) { ignored.add(i); } } return ignored; } private static final Comparator> cmp = new Comparator>() { @Override public int compare( Entry o1, Entry o2) { return Integer.compare(o1.getValue(), o2.getValue()); } }; } private static class IndexedFactSet { private static final AtomicInteger idCnt = new AtomicInteger(); private final int id; private final NavigableSet s; private final AtomicInteger cnt = new AtomicInteger(); private final List comparatorOrder; private static final TupleComparatorGenerator gen = new TupleComparatorGenerator(); public static IndexedFactSet make(BindingType[] pat) { List order = new ArrayList<>(); for (int i = 0; i < pat.length; ++i) { if (pat[i].isBound()) { order.add(i); } } for (int i = 0; i < pat.length; ++i) { if (pat[i].isFree()) { order.add(i); } } return make(order); } private static IndexedFactSet make(List order) { int[] a = new int[order.size()]; for (int i = 0; i < a.length; ++i) { a[i] = order.get(i); } Comparator cmp; if (Configuration.genComparators) { try { cmp = gen.generate(a); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new AssertionError(e); } } else { cmp = new TermArrayComparator(a); } return new IndexedFactSet(new ConcurrentSkipListSet<>(cmp), order); } public int comparatorLength() { return comparatorOrder.size(); } public Iterable getAll() { return s; } public void clear() { s.clear(); cnt.set(0); } public boolean isEmpty() { return s.isEmpty(); } private IndexedFactSet(NavigableSet s, List comparatorOrder) { this.s = s; this.comparatorOrder = comparatorOrder; this.id = idCnt.getAndIncrement(); } public int getId() { return id; } public boolean add(Term[] arr) { boolean modified = s.add(arr); if (modified) { cnt.incrementAndGet(); } return modified; } public boolean addAll(Iterable tups) { boolean modified = false; int delta = 0; for (Term[] tup : tups) { if (s.add(tup)) { modified = true; delta++; } } if (modified) { cnt.addAndGet(delta); } return modified; } public int count() { return cnt.get(); } public Iterable lookup(Term[] tup, BindingType[] pat) { Term[] lower = new Term[tup.length]; Term[] upper = new Term[tup.length]; for (int i = 0; i < tup.length; ++i) { if (pat[i].isBound()) { lower[i] = tup[i]; upper[i] = tup[i]; } else { lower[i] = Terms.minTerm; upper[i] = Terms.maxTerm; } } return s.subSet(lower, true, upper, true); } public boolean contains(Term[] tup) { return s.contains(tup); } @Override public String toString() { String str = "[\n\t"; str += "\t#" + id + " " + comparatorOrder + "\n"; for (Term[] tup : s) { str += "\n\t"; str += Arrays.toString(tup); } return str + "\n]"; } } private static class TermArrayComparator implements Comparator { private final int[] pat; public TermArrayComparator(int[] pat) { this.pat = pat; } @Override public int compare(Term[] o1, Term[] o2) { for (int i = 0; i < pat.length; i++) { int j = pat[i]; int x = o1[j].getId(); int y = o2[j].getId(); if (x < y) { return -1; } else if (x > y) { return 1; } } return 0; } } public class IndexInfo { private final int indexId; private final List comparatorOrder; private final BindingType[] pat; private IndexInfo(int indexId, List comparatorOrder, BindingType[] pat) { this.indexId = indexId; this.comparatorOrder = Collections.unmodifiableList(comparatorOrder); this.pat = pat; } public List getComparatorOrder() { return comparatorOrder; } public BindingType[] getPattern() { return pat; } public int getIndexId() { return indexId; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/db/TupleComparatorGenerator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.db; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.util.IntArrayWrapper; import edu.harvard.seas.pl.formulog.util.Pair; import java.lang.reflect.InvocationTargetException; import java.util.Comparator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.bcel.Const; import org.apache.bcel.generic.ASTORE; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.BranchInstruction; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ILOAD; import org.apache.bcel.generic.ISTORE; import org.apache.bcel.generic.InstructionConst; import org.apache.bcel.generic.InstructionFactory; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.PUSH; import org.apache.bcel.generic.Type; public class TupleComparatorGenerator extends ClassLoader { private static final Type objectType = new ObjectType("java.lang.Object"); private static final Type termType = new ObjectType("edu.harvard.seas.pl.formulog.ast.Term"); private static final Type termArrayType = new ArrayType(termType, 1); private final AtomicInteger cnt = new AtomicInteger(); private Map> memo = new ConcurrentHashMap<>(); public Comparator generate(int[] accessPat) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { IntArrayWrapper key = new IntArrayWrapper(accessPat); Comparator cmp = memo.get(key); if (cmp == null) { cmp = generate1(accessPat); Comparator cmp2 = memo.putIfAbsent(key, cmp); if (cmp2 != null) { cmp = cmp2; } } return cmp; } @SuppressWarnings("unchecked") public Comparator generate1(int[] accessPat) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { String className = "edu.harvard.seas.pl.formulog.db.CustomComparator" + cnt.getAndIncrement(); ClassGen classGen = new ClassGen( className, "java.lang.Object", "", Const.ACC_PUBLIC | Const.ACC_SUPER, new String[] {"java.util.Comparator"}); classGen.addEmptyConstructor(Const.ACC_PUBLIC); addCompareMethod(classGen, accessPat); byte[] data = classGen.getJavaClass().getBytes(); Class c = defineClass(className, data, 0, data.length); return (Comparator) c.getDeclaredConstructor().newInstance(); } private void addCompareMethod(ClassGen cg, int[] accessPat) { InstructionFactory f = new InstructionFactory(cg); InstructionList il = new InstructionList(); il.append(genCast(f, 1)); il.append(genCast(f, 2)); InstructionHandle ih = null; BranchInstruction br = null; for (int i : accessPat) { Pair p = genComparison(f, i); ih = il.append(p.fst()); if (br != null) { br.setTarget(ih); } br = p.snd(); } ih = il.append(new PUSH(cg.getConstantPool(), 0)); if (br != null) { br.setTarget(ih); } il.append(InstructionConst.IRETURN); MethodGen mg = new MethodGen( Const.ACC_PUBLIC, Type.INT, new Type[] {objectType, objectType}, new String[] {"xs", "ys"}, "compare", cg.getClassName(), il, cg.getConstantPool()); mg.setMaxStack(); mg.setMaxLocals(); cg.addMethod(mg.getMethod()); } private InstructionList genCast(InstructionFactory f, int argn) { assert argn == 1 || argn == 2; InstructionList il = new InstructionList(); il.append(InstructionFactory.createLoad(objectType, argn)); il.append(f.createCast(objectType, termArrayType)); il.append(new ASTORE(argn)); return il; } private Pair genComparison(InstructionFactory f, int idx) { InstructionList il = new InstructionList(); il.append(genLoad(f, 1, idx)); il.append(genLoad(f, 2, idx)); Pair p = genICmp(f, true); il.append(p.fst()); BranchInstruction br = p.snd(); p = genICmp(f, false); br.setTarget(il.append(p.fst())); return new Pair<>(il, p.snd()); } private InstructionList genLoad(InstructionFactory f, int argn, int idx) { assert argn == 1 || argn == 2; InstructionList il = new InstructionList(); il.append(argn == 1 ? InstructionConst.ALOAD_1 : InstructionConst.ALOAD_2); il.append(new PUSH(f.getConstantPool(), idx)); il.append(InstructionConst.AALOAD); il.append( f.createInvoke( "edu.harvard.seas.pl.formulog.ast.Term", "getId", Type.INT, new Type[] {}, Const.INVOKEINTERFACE)); il.append(new ISTORE(argn + 2)); return il; } private Pair genICmp(InstructionFactory f, boolean firstCmp) { InstructionList il = new InstructionList(); il.append(new ILOAD(3)); il.append(new ILOAD(4)); BranchInstruction br = InstructionFactory.createBranchInstruction( firstCmp ? Const.IF_ICMPGE : Const.IF_ICMPLE, null); il.append(br); il.append(new PUSH(f.getConstantPool(), firstCmp ? -1 : 1)); il.append(InstructionConst.IRETURN); return new Pair<>(il, br); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/AbstractStratumEvaluator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.OverwriteSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.AbstractFJPTask; import edu.harvard.seas.pl.formulog.util.CountingFJP; import edu.harvard.seas.pl.formulog.util.SharedLong; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * This class represents an abstract stratum evaluation method (e.g., semi-naive evaluation, eager * evaluation). It includes functionality common to different evaluation techniques. Most notably, * it splits a rule into a prefix (which conceptually contains no loops and can be processed * sequentially) and a suffix (which contains loops that should be parallelized); additionally, it * contains the code for evaluating the suffix of a rule, which is the "hot loop" during Datalog * evaluation. */ public abstract class AbstractStratumEvaluator implements StratumEvaluator { protected final Set firstRoundRules = new HashSet<>(); protected final Map> laterRoundRules = new HashMap<>(); protected final Map splitPositions = new HashMap<>(); protected final Map checkPosition = new HashMap<>(); protected final CountingFJP exec; public AbstractStratumEvaluator(Iterable rules, CountingFJP exec) { processRules(rules); this.exec = exec; } private void processRules(Iterable rules) { SmtCallFinder scf = new SmtCallFinder(); for (IndexedRule rule : rules) { RelationSymbol delta = EvalUtil.findDelta(rule); if (delta != null) { Util.lookupOrCreate(laterRoundRules, delta, () -> new HashSet<>()).add(rule); } else { firstRoundRules.add(rule); } boolean[] positions = findSplitPositions(rule, scf); splitPositions.put(rule, positions); checkPosition.put(rule, findCheckPosition(rule)); } } private static boolean[] findSplitPositions(IndexedRule rule, SmtCallFinder scf) { int len = rule.getBodySize(); boolean[] splitPositions = new boolean[len]; boolean smtCallComing = scf.containsSmtCall(rule.getHead()); for (int i = len - 1; i >= 0; --i) { SimpleLiteral l = rule.getBody(i); if (smtCallComing && l instanceof SimplePredicate && !((SimplePredicate) l).isNegated()) { splitPositions[i] = true; smtCallComing = scf.containsSmtCall(l); } else { smtCallComing = smtCallComing || scf.containsSmtCall(l); } } return splitPositions; } private static int findCheckPosition(IndexedRule rule) { var headVars = rule.getHead().varSet(); Set bound = new HashSet<>(); int i = 0; for (; i < rule.getBodySize(); ++i) { if (bound.containsAll(headVars)) { break; } rule.getBody(i).varSet(bound); } return i; } protected abstract void reportFact(RelationSymbol sym, Term[] args); protected abstract boolean checkFact( RelationSymbol sym, Term[] args, Substitution s, Term[] scratch) throws EvaluationException; protected abstract Iterable> lookup( IndexedRule r, int pos, OverwriteSubstitution s, boolean split) throws EvaluationException; protected static final boolean recordRuleDiagnostics = Configuration.recordRuleDiagnostics; @SuppressWarnings("serial") protected class RuleSuffixEvaluator extends AbstractFJPTask { final IndexedRule rule; final SimplePredicate head; final SimpleLiteral[] body; final int startPos; final OverwriteSubstitution s; final Iterator> it; final Term[] scratch; protected RuleSuffixEvaluator( IndexedRule rule, SimplePredicate head, SimpleLiteral[] body, int pos, OverwriteSubstitution s, Iterator> it, Term[] scratch) { super(exec); this.rule = rule; this.head = head; this.body = body; this.startPos = pos; this.s = s; this.it = it; this.scratch = scratch; if (Configuration.recordDetailedWork) { Configuration.workItems.increment(); } } protected RuleSuffixEvaluator( IndexedRule rule, int pos, OverwriteSubstitution s, Iterator> it, Term[] scratch) { super(exec); this.rule = rule; this.head = rule.getHead(); SimpleLiteral[] bd = new SimpleLiteral[rule.getBodySize()]; for (int i = 0; i < bd.length; ++i) { bd[i] = rule.getBody(i); } this.body = bd; this.startPos = pos; this.s = s; this.it = it; this.scratch = scratch; if (Configuration.recordDetailedWork) { Configuration.workItems.increment(); } } @Override public void doTask() throws EvaluationException { long start = 0; if (recordRuleDiagnostics) { start = System.currentTimeMillis(); } Iterable tups = it.next(); if (it.hasNext()) { exec.recursivelyAddTask( new RuleSuffixEvaluator(rule, head, body, startPos, s.copy(), it, scratch.clone())); } try { for (Term[] tup : tups) { evaluate(tup); } } catch (UncheckedEvaluationException e) { throw new EvaluationException( "Exception raised while evaluating the rule: " + rule + "\n\n" + e.getMessage()); } if (recordRuleDiagnostics) { long end = System.currentTimeMillis(); Configuration.recordRuleSuffixTime(rule, end - start); } } private void evaluate(Term[] ans) throws UncheckedEvaluationException { SimplePredicate p = (SimplePredicate) body[startPos]; updateBinding(p, ans); int pos = startPos + 1; @SuppressWarnings("unchecked") Iterator[] stack = new Iterator[rule.getBodySize()]; boolean movingRight = true; var checkPos = checkPosition.get(rule); while (pos > startPos) { try { if (movingRight && checkPos == pos && !checkFact(head.getSymbol(), head.getArgs(), s, scratch)) { pos--; movingRight = false; } } catch (EvaluationException e) { throw new UncheckedEvaluationException( "Exception raised while evaluating the literal: " + head + "\n\n" + e.getMessage()); } if (pos == body.length) { reportFact(head.getSymbol(), scratch); pos--; movingRight = false; } else if (movingRight) { SimpleLiteral l = body[pos]; try { switch (l.getTag()) { case ASSIGNMENT: ((Assignment) l).assign(s); pos++; break; case CHECK: if (((Check) l).check(s)) { pos++; } else { pos--; movingRight = false; } break; case DESTRUCTOR: if (((Destructor) l).destruct(s)) { pos++; } else { pos--; movingRight = false; } break; case PREDICATE: Iterator> tups = lookup(rule, pos, s, Configuration.parallelizeInnerLoops).iterator(); if (((SimplePredicate) l).isNegated()) { if (!tups.hasNext()) { pos++; } else { pos--; movingRight = false; } } else { if (tups.hasNext()) { stack[pos] = tups.next().iterator(); if (tups.hasNext()) { exec.recursivelyAddTask( new RuleSuffixEvaluator( rule, head, body, pos, s.copy(), tups, scratch.clone())); } // No need to do anything else: we'll hit the right case on the next iteration. } else { pos--; } movingRight = false; } break; } } catch (EvaluationException e) { throw new UncheckedEvaluationException( "Exception raised while evaluating the literal: " + l + "\n\n" + e.getMessage()); } } else { Iterator it = stack[pos]; if (it != null && it.hasNext()) { ans = it.next(); updateBinding((SimplePredicate) rule.getBody(pos), ans); movingRight = true; pos++; } else { stack[pos] = null; pos--; } } } } private void updateBinding(SimplePredicate p, Term[] ans) { if (Configuration.recordWork) { Configuration.work.increment(); } if (Configuration.recordDetailedWork) { Util.lookupOrCreate(Configuration.workPerRule, rule, SharedLong::new).increment(); } Term[] args = p.getArgs(); BindingType[] pat = p.getBindingPattern(); for (int i = 0; i < pat.length; ++i) { if (pat[i].isFree()) { s.put((Var) args[i], ans[i]); } } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/EagerStratumEvaluator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.db.SortedIndexedFactDb; import edu.harvard.seas.pl.formulog.eval.SemiNaiveRule.DeltaSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.OverwriteSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.AbstractFJPTask; import edu.harvard.seas.pl.formulog.util.CountingFJP; import edu.harvard.seas.pl.formulog.util.SharedLong; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; import java.util.Collections; import java.util.Iterator; import java.util.Set; /** * This class implements eager evaluation of a stratum, an alternative evaluation strategy to * traditional semi-naive Datalog evaluation. Instead of batching newly derived tuples into explicit * rounds of evaluation (like semi-naive evaluation), eager evaluation eagerly pursues the * consequences of newly derived tuples; this is achieved by immediately submitting work items for * each newly derived tuple to a work-stealing thread pool that selects the next item to evaluate * using the LIFO order. */ public final class EagerStratumEvaluator extends AbstractStratumEvaluator { private final SortedIndexedFactDb db; private final Set trackedRelations; static final int taskSize = Configuration.taskSize; static final int smtTaskSize = Configuration.smtTaskSize; public EagerStratumEvaluator( SortedIndexedFactDb db, Iterable rules, CountingFJP exec, Set trackedRelations) { super(rules, exec); this.db = db; this.trackedRelations = trackedRelations; } @Override public void evaluate() throws EvaluationException { for (IndexedRule r : firstRoundRules) { exec.externallyAddTask(new RulePrefixEvaluator(r, null)); } exec.blockUntilFinished(); if (exec.hasFailed()) { throw exec.getFailureCause(); } } @Override protected void reportFact(RelationSymbol sym, Term[] args) { Term[] copy = args.clone(); if (db.add(sym, copy)) { Set rs = laterRoundRules.get(sym); if (rs != null) { for (IndexedRule r : rs) { exec.recursivelyAddTask(new RulePrefixEvaluator(r, copy)); } } if (trackedRelations.contains(sym)) { System.err.println("[TRACKED] " + UserPredicate.make(sym, copy, false)); } if (Configuration.recordDetailedWork) { Configuration.newDerivs.increment(); } } else if (Configuration.recordDetailedWork) { Configuration.dupDerivs.increment(); } } @Override protected boolean checkFact(RelationSymbol sym, Term[] args, Substitution s, Term[] scratch) throws EvaluationException { for (int i = 0; i < args.length; ++i) { scratch[i] = args[i].normalize(s); } return !db.hasFact(sym, scratch); } @Override protected Iterable> lookup( IndexedRule r, int pos, OverwriteSubstitution s, boolean split) throws EvaluationException { SimplePredicate predicate = (SimplePredicate) r.getBody(pos); int idx = r.getDbIndex(pos); Term[] args = predicate.getArgs(); Term[] key = new Term[args.length]; BindingType[] pat = predicate.getBindingPattern(); for (int i = 0; i < args.length; ++i) { if (pat[i].isBound()) { key[i] = args[i].normalize(s); } else { key[i] = args[i]; } } RelationSymbol sym = predicate.getSymbol(); assert !(sym instanceof DeltaSymbol); Iterable ans = db.get(sym, key, idx); if (split) { boolean smtSplit = splitPositions.get(r)[pos]; int targetSize = smtSplit ? smtTaskSize : taskSize; return Util.splitIterable(ans, targetSize); } else if (ans.iterator().hasNext()) { return Collections.singletonList(ans); } else { return Collections.emptyList(); } } @SuppressWarnings("serial") private class RulePrefixEvaluator extends AbstractFJPTask { private final IndexedRule rule; private final Term[] deltaArgs; protected RulePrefixEvaluator(IndexedRule rule, Term[] deltaArgs) { super(exec); this.rule = rule; this.deltaArgs = deltaArgs; if (Configuration.recordDetailedWork) { Configuration.workItems.increment(); } } private final boolean handleDelta(SimplePredicate pred, Substitution s) throws EvaluationException { BindingType[] bindings = pred.getBindingPattern(); Term[] args = pred.getArgs(); int i = 0; for (BindingType b : bindings) { Term arg = args[i]; if (b.isFree()) { assert arg instanceof Var; s.put((Var) arg, deltaArgs[i]); } else if (b.isBound()) { if (!arg.normalize(s).equals(deltaArgs[i])) { return false; } } ++i; } if (Configuration.recordWork) { Configuration.work.increment(); } if (Configuration.recordDetailedWork) { Util.lookupOrCreate(Configuration.workPerRule, rule, SharedLong::new).increment(); } return true; } @Override public final void doTask() throws EvaluationException { long start = 0; if (recordRuleDiagnostics) { start = System.currentTimeMillis(); } try { evaluate(); } catch (EvaluationException e) { throw new EvaluationException( "Exception raised while evaluating the rule:\n" + rule + "\n\n" + e.getMessage()); } if (recordRuleDiagnostics) { long end = System.currentTimeMillis(); Configuration.recordRulePrefixTime(rule, end - start); } } private void evaluate() throws EvaluationException { int len = rule.getBodySize(); int pos = 0; OverwriteSubstitution s = new OverwriteSubstitution(); SimplePredicate head = rule.getHead(); var scratch = new Term[head.getSymbol().getArity()]; var checkPos = checkPosition.get(rule); loop: for (; pos <= len; ++pos) { SimpleLiteral l = head; if (checkPos == pos && !checkFact(head.getSymbol(), head.getArgs(), s, scratch)) { return; } if (pos == len) { reportFact(head.getSymbol(), scratch); return; } l = rule.getBody(pos); try { switch (l.getTag()) { case ASSIGNMENT: ((Assignment) l).assign(s); break; case CHECK: if (!((Check) l).check(s)) { return; } break; case DESTRUCTOR: if (!((Destructor) l).destruct(s)) { return; } break; case PREDICATE: SimplePredicate p = (SimplePredicate) l; if (p.isNegated()) { if (lookup(rule, pos, s, false).iterator().hasNext()) { return; } } else { RelationSymbol sym = p.getSymbol(); if (!(sym instanceof DeltaSymbol)) { break loop; } if (!handleDelta(p, s)) { return; } } break; } } catch (EvaluationException e) { throw new EvaluationException( "Exception raised while evaluating the literal: " + l + "\n\n" + e.getMessage()); } } Iterator> tups = lookup(rule, pos, s, true).iterator(); if (tups.hasNext()) { new RuleSuffixEvaluator(rule, pos, s, tups, scratch.clone()).doTask(); } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/EvalUtil.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.eval.SemiNaiveRule.DeltaSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteralVisitor; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; public final class EvalUtil { private EvalUtil() { throw new AssertionError("impossible"); } public static RelationSymbol findDelta(IndexedRule rule) { for (SimpleLiteral l : rule) { RelationSymbol delta = l.accept( new SimpleLiteralVisitor() { @Override public RelationSymbol visit(Assignment assignment, Void input) { return null; } @Override public RelationSymbol visit(Check check, Void input) { return null; } @Override public RelationSymbol visit(Destructor destructor, Void input) { return null; } @Override public RelationSymbol visit(SimplePredicate predicate, Void input) { if (predicate.getSymbol() instanceof DeltaSymbol) { return ((DeltaSymbol) predicate.getSymbol()).getBaseSymbol(); } else { return null; } } }, null); if (delta != null) { return delta; } } return null; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/Evaluation.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.UserPredicate; public interface Evaluation { void run() throws EvaluationException; EvaluationResult getResult(); boolean hasQuery(); UserPredicate getQuery(); Program getInputProgram(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/EvaluationException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; public class EvaluationException extends Exception { private static final long serialVersionUID = 959646507426445054L; public EvaluationException() {} public EvaluationException(String message) { super(message); } public EvaluationException(Throwable cause) { super(cause); } public EvaluationException(String message, Throwable cause) { super(message, cause); } public EvaluationException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/EvaluationResult.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import java.util.Set; public interface EvaluationResult { Iterable getAll(RelationSymbol sym); Iterable getQueryAnswer(); Set getSymbols(); int getCount(RelationSymbol sym); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/IndexedRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteralVisitor; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Function; public class IndexedRule implements Rule { private final SimplePredicate head; private final List body; private final List idxs; private IndexedRule( Rule rule, Function makeIndex) { head = rule.getHead(); body = Util.iterableToList(rule); idxs = createIndexes(makeIndex); } public static IndexedRule make( Rule rule, Function makeIndex) { return new IndexedRule(rule, makeIndex); } private List createIndexes(Function makeIndex) { List idxs = new ArrayList<>(); for (SimpleLiteral a : body) { idxs.add( a.accept( new SimpleLiteralVisitor() { @Override public Integer visit(Assignment assignment, Void input) { return null; } @Override public Integer visit(Check check, Void input) { return null; } @Override public Integer visit(Destructor destructor, Void input) { return null; } @Override public Integer visit(SimplePredicate predicate, Void input) { return makeIndex.apply(predicate); } }, null)); } return idxs; } @Override public SimplePredicate getHead() { return head; } @Override public int getBodySize() { return body.size(); } @Override public SimpleLiteral getBody(int idx) { return body.get(idx); } public Integer getDbIndex(int idx) { return idxs.get(idx); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(head); sb.append(" :-"); if (body.size() == 1) { sb.append(" "); } else { sb.append("\n\t"); } int i = 0; for (Iterator it = body.iterator(); it.hasNext(); ) { Integer idx = idxs.get(i); if (idx != null) { sb.append(idx + ": "); } sb.append(it.next()); if (it.hasNext()) { sb.append(",\n\t"); } ++i; } sb.append("."); return sb.toString(); } @Override public Iterator iterator() { return body.iterator(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/PredicateFunctionSetter.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.BoolTerm; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.db.IndexedFactDb; import edu.harvard.seas.pl.formulog.db.IndexedFactDbBuilder; import edu.harvard.seas.pl.formulog.functions.DummyFunctionDef; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.functions.PredicateFunctionDef; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.PredicateFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.FunctorType; import java.util.HashSet; import java.util.Set; public class PredicateFunctionSetter { private final FunctionDefManager defs; private final IndexedFactDbBuilder dbb; private IndexedFactDb db; Set visitedFunctions = new HashSet<>(); public PredicateFunctionSetter(FunctionDefManager funcs, IndexedFactDbBuilder dbb) { this.defs = funcs; this.dbb = dbb; for (FunctionSymbol sym : funcs.getFunctionSymbols()) { FunctionDef def = funcs.lookup(sym); if (def instanceof UserFunctionDef) { preprocess(((UserFunctionDef) def).getBody()); } } } public void setDb(IndexedFactDb db) { assert this.db == null; this.db = db; } public void preprocess(Rule r) { preprocess(r.getHead()); for (ComplexLiteral l : r) { preprocess(l); } } public void preprocess(ComplexLiteral l) { for (Term arg : l.getArgs()) { preprocess(arg); } } public void preprocess(Term t) { t.accept(tv, null); } private final TermVisitor tv = new TermVisitor() { @Override public Void visit(Var t, Void in) { return null; } @Override public Void visit(Constructor c, Void in) { for (Term arg : c.getArgs()) { arg.accept(this, in); } return null; } @Override public Void visit(Primitive p, Void in) { return null; } @Override public Void visit(Expr e, Void in) { e.accept(ev, in); return null; } }; private final ExprVisitor ev = new ExprVisitor() { @Override public Void visit(MatchExpr matchExpr, Void in) { matchExpr.getMatchee().accept(tv, in); for (MatchClause cl : matchExpr) { cl.getLhs().accept(tv, in); cl.getRhs().accept(tv, in); } return null; } @Override public Void visit(FunctionCall funcCall, Void in) { for (Term arg : funcCall.getArgs()) { arg.accept(tv, in); } FunctionSymbol sym = funcCall.getSymbol(); if (!visitedFunctions.add(sym) || sym instanceof BuiltInFunctionSymbol) { return null; } FunctionDef def = defs.lookup(sym); if (sym instanceof PredicateFunctionSymbol) { DummyFunctionDef dummy = (DummyFunctionDef) def; setPredicateFunction(dummy); } else if (def instanceof UserFunctionDef) { ((UserFunctionDef) def).getBody().accept(tv, in); } return null; } @Override public Void visit(LetFunExpr funcDef, Void in) { throw new AssertionError("impossible"); } @Override public Void visit(Fold fold, Void in) { fold.getShamCall().accept(this, in); return null; } }; private void setPredicateFunction(DummyFunctionDef def) { if (def.getDef() != null) { return; } PredicateFunctionSymbol sym = (PredicateFunctionSymbol) def.getSymbol(); BindingType[] bindings = sym.getBindings(); assert bindings != null; BindingType[] bindingsForIndex = turnIgnoredToFree(bindings); int idx = dbb.makeIndex(sym.getPredicateSymbol(), bindingsForIndex); FunctorType type = sym.getCompileTimeType(); Term[] paddedArgs = padArgs(sym); FunctionDef innerDef; if (type.getRetType().equals(BuiltInTypes.bool)) { innerDef = makePredicate(sym, paddedArgs, idx); } else { innerDef = makeAggregate(sym, paddedArgs, idx, bindingsForIndex); } def.setDef(innerDef); } private static BindingType[] turnIgnoredToFree(BindingType[] bindings) { BindingType[] bindings2 = new BindingType[bindings.length]; for (int i = 0; i < bindings.length; ++i) { bindings2[i] = bindings[i]; if (bindings2[i].isIgnored()) { bindings2[i] = BindingType.FREE; } } return bindings2; } private FunctionDef makePredicate(PredicateFunctionSymbol funcSym, Term[] paddedArgs, int idx) { RelationSymbol predSym = funcSym.getPredicateSymbol(); return new PredicateFunctionDef() { @Override public FunctionSymbol getSymbol() { return funcSym; } @Override public Term evaluate(Term[] args) throws EvaluationException { args = fillInPaddedArgs(funcSym, paddedArgs, args); boolean b = db.get(predSym, args, idx).iterator().hasNext(); return BoolTerm.mk(b); } @Override public int getIndex() { return idx; } @Override public BindingType[] getBindingsForIndex() { return funcSym.getBindings(); } }; } private FunctionDef makeAggregate( PredicateFunctionSymbol funcSym, Term[] paddedArgs, int idx, BindingType[] bindingsUsedForIndex) { RelationSymbol predSym = funcSym.getPredicateSymbol(); int arity = 0; BindingType[] bindings = funcSym.getBindings(); for (BindingType b : bindings) { if (b.isFree()) { arity++; } } final int arity2 = arity; ConstructorSymbol tupSym = (arity > 1) ? GlobalSymbolManager.lookupTupleSymbol(arity) : null; return new PredicateFunctionDef() { @Override public FunctionSymbol getSymbol() { return funcSym; } @Override public Term evaluate(Term[] args) throws EvaluationException { args = fillInPaddedArgs(funcSym, paddedArgs, args); Term tail = Constructors.makeZeroAry(BuiltInConstructorSymbol.NIL); for (Term[] fact : db.get(predSym, args, idx)) { Term[] proj = new Term[arity2]; int j = 0; for (int i = 0; i < bindings.length; ++i) { if (bindings[i].isFree()) { proj[j] = fact[i]; ++j; } } Term elt = tupSym == null ? proj[0] : Constructors.make(tupSym, proj); tail = Constructors.make(BuiltInConstructorSymbol.CONS, new Term[] {elt, tail}); } return tail; } @Override public int getIndex() { return idx; } @Override public BindingType[] getBindingsForIndex() { return bindingsUsedForIndex; } }; } private Term[] padArgs(PredicateFunctionSymbol funcSym) { RelationSymbol predSym = funcSym.getPredicateSymbol(); Term[] padded = new Term[predSym.getArity()]; for (int i = 0; i < padded.length; ++i) { padded[i] = Var.fresh(); } return padded; } private Term[] fillInPaddedArgs( PredicateFunctionSymbol funcSym, Term[] paddedArgs, Term[] actualArgs) { RelationSymbol predSym = funcSym.getPredicateSymbol(); Term[] newArgs = new Term[predSym.getArity()]; int i = 0; int j = 0; for (BindingType b : funcSym.getBindings()) { if (b.isBound()) { newArgs[i] = actualArgs[j]; j++; } else { newArgs[i] = paddedArgs[i]; } i++; } return newArgs; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/RoundBasedStratumEvaluator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.db.SortedIndexedFactDb; import edu.harvard.seas.pl.formulog.eval.SemiNaiveRule.DeltaSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.OverwriteSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.AbstractFJPTask; import edu.harvard.seas.pl.formulog.util.CountingFJP; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.ast.Assignment; import edu.harvard.seas.pl.formulog.validating.ast.Check; import edu.harvard.seas.pl.formulog.validating.ast.Destructor; import edu.harvard.seas.pl.formulog.validating.ast.SimpleLiteral; import edu.harvard.seas.pl.formulog.validating.ast.SimplePredicate; import java.time.LocalDateTime; import java.util.Collections; import java.util.Iterator; import java.util.Set; import org.apache.commons.lang3.time.StopWatch; /** * This class implements traditional semi-naive evaluation of a stratum, in which rule atoms refer * to auxiliary relations indexed by round of the fixpoint evaluation. For example, if the predicate * p is recursive in the current stratum, going into round i+1, there will be the relation * delta_i(p) holding the p facts derived in round i, and the relation p_{i+1} holding the entire p * relation at the beginning of round i+1. Unlike some versions of semi-naive evaluation, we do not * use the auxiliary relation p_i (that is, the relation p as it stood at the beginning of the * previous round) when adorning non-linearly recursive rules. */ public final class RoundBasedStratumEvaluator extends AbstractStratumEvaluator { private final int stratumNum; private final SortedIndexedFactDb db; private SortedIndexedFactDb deltaDb; private SortedIndexedFactDb nextDeltaDb; private final Set trackedRelations; private volatile boolean changed; private static final int taskSize = Configuration.taskSize; private static final int smtTaskSize = Configuration.smtTaskSize; public RoundBasedStratumEvaluator( int stratumNum, SortedIndexedFactDb db, SortedIndexedFactDb deltaDb, SortedIndexedFactDb nextDeltaDb, Iterable rules, CountingFJP exec, Set trackedRelations) { super(rules, exec); this.stratumNum = stratumNum; this.db = db; this.deltaDb = deltaDb; this.nextDeltaDb = nextDeltaDb; this.trackedRelations = trackedRelations; } @Override public void evaluate() throws EvaluationException { this.deltaDb.clear(); this.nextDeltaDb.clear(); final boolean oneRuleAtATime = Configuration.oneRuleAtATime; int round = 0; StopWatch watch = recordRoundStart(round); for (IndexedRule r : firstRoundRules) { exec.externallyAddTask(new RulePrefixEvaluator(r)); if (oneRuleAtATime) { exec.blockUntilFinishedExn(); } } exec.blockUntilFinishedExn(); recordRoundEnd(round, watch); updateDbs(); while (changed) { round++; watch = recordRoundStart(round); changed = false; for (RelationSymbol delta : laterRoundRules.keySet()) { if (!deltaDb.isEmpty(delta)) { for (IndexedRule r : laterRoundRules.get(delta)) { exec.externallyAddTask(new RulePrefixEvaluator(r)); if (oneRuleAtATime) { exec.blockUntilFinishedExn(); } } } } exec.blockUntilFinishedExn(); recordRoundEnd(round, watch); updateDbs(); } } @Override protected final void reportFact(RelationSymbol sym, Term[] args) { var copy = args.clone(); if (nextDeltaDb.add(sym, copy)) { changed = true; if (trackedRelations.contains(sym)) { System.err.println("[TRACKED] " + UserPredicate.make(sym, copy, false)); } if (Configuration.recordDetailedWork) { Configuration.newDerivs.increment(); } } else if (Configuration.recordDetailedWork) { Configuration.dupDerivs.increment(); } } @Override protected final boolean checkFact(RelationSymbol sym, Term[] args, Substitution s, Term[] scratch) throws EvaluationException { for (int i = 0; i < args.length; ++i) { scratch[i] = args[i].normalize(s); } return !db.hasFact(sym, scratch) && !nextDeltaDb.hasFact(sym, scratch); } private void updateDbs() { StopWatch watch = recordDbUpdateStart(); for (RelationSymbol sym : nextDeltaDb.getSymbols()) { if (nextDeltaDb.isEmpty(sym)) { continue; } Iterable> answers = Util.splitIterable(nextDeltaDb.getAll(sym), taskSize); exec.externallyAddTask(new UpdateDbTask(sym, answers.iterator())); } exec.blockUntilFinished(); SortedIndexedFactDb tmp = deltaDb; deltaDb = nextDeltaDb; nextDeltaDb = tmp; nextDeltaDb.clear(); recordDbUpdateEnd(watch); } @SuppressWarnings("serial") private class UpdateDbTask extends AbstractFJPTask { final RelationSymbol sym; final Iterator> it; protected UpdateDbTask(RelationSymbol sym, Iterator> it) { super(exec); this.sym = sym; this.it = it; } @Override public void doTask() throws EvaluationException { Iterable tups = it.next(); if (it.hasNext()) { exec.recursivelyAddTask(new UpdateDbTask(sym, it)); } db.addAll(sym, tups); } } @Override protected final Iterable> lookup( IndexedRule r, int pos, OverwriteSubstitution s, boolean split) throws EvaluationException { SimplePredicate predicate = (SimplePredicate) r.getBody(pos); int idx = r.getDbIndex(pos); Term[] args = predicate.getArgs(); Term[] key = new Term[args.length]; BindingType[] pat = predicate.getBindingPattern(); for (int i = 0; i < args.length; ++i) { if (pat[i].isBound()) { key[i] = args[i].normalize(s); } else { key[i] = args[i]; } } RelationSymbol sym = predicate.getSymbol(); Iterable ans; if (sym instanceof DeltaSymbol) { ans = deltaDb.get(((DeltaSymbol) sym).getBaseSymbol(), key, idx); } else { ans = db.get(sym, key, idx); } if (split) { boolean smtSplit = splitPositions.get(r)[pos]; int targetSize = smtSplit ? smtTaskSize : taskSize; return Util.splitIterable(ans, targetSize); } else if (ans.iterator().hasNext()) { return Collections.singletonList(ans); } else { return Collections.emptyList(); } } @SuppressWarnings("serial") private class RulePrefixEvaluator extends AbstractFJPTask { private final IndexedRule rule; protected RulePrefixEvaluator(IndexedRule rule) { super(exec); this.rule = rule; if (Configuration.recordDetailedWork) { Configuration.workItems.increment(); } } @Override public void doTask() throws EvaluationException { long start = 0; if (recordRuleDiagnostics) { start = System.currentTimeMillis(); } try { evaluate(); } catch (EvaluationException e) { throw new EvaluationException( "Exception raised while evaluating the rule:\n" + rule + "\n\n" + e.getMessage()); } if (recordRuleDiagnostics) { long end = System.currentTimeMillis(); Configuration.recordRulePrefixTime(rule, end - start); } } private void evaluate() throws EvaluationException { int len = rule.getBodySize(); int pos = 0; OverwriteSubstitution s = new OverwriteSubstitution(); SimplePredicate head = rule.getHead(); Term[] scratch = new Term[head.getSymbol().getArity()]; int checkPos = checkPosition.get(rule); loop: for (; pos <= len; ++pos) { SimpleLiteral l = head; if (checkPos == pos && !checkFact(head.getSymbol(), head.getArgs(), s, scratch)) { return; } if (pos == len) { reportFact(head.getSymbol(), scratch); return; } l = rule.getBody(pos); try { switch (l.getTag()) { case ASSIGNMENT: ((Assignment) l).assign(s); break; case CHECK: if (!((Check) l).check(s)) { return; } break; case DESTRUCTOR: if (!((Destructor) l).destruct(s)) { return; } break; case PREDICATE: SimplePredicate p = (SimplePredicate) l; if (p.isNegated()) { if (lookup(rule, pos, s, false).iterator().hasNext()) { return; } } else { // Stop on the first positive user predicate. break loop; } break; } } catch (EvaluationException e) { throw new EvaluationException( "Exception raised while evaluating the literal: " + l + "\n\n" + e.getMessage()); } } Iterator> tups = lookup(rule, pos, s, true).iterator(); if (tups.hasNext()) { new RuleSuffixEvaluator(rule, pos, s, tups, scratch.clone()).doTask(); } } } private StopWatch recordRoundStart(int round) { if (!Configuration.debugRounds) { return null; } System.err.println("#####"); System.err.println("[START] Stratum " + stratumNum + ", round " + round); LocalDateTime now = LocalDateTime.now(); System.err.println("Start: " + now); StopWatch watch = new StopWatch(); watch.start(); return watch; } private void recordRoundEnd(int round, StopWatch watch) { if (watch == null) { return; } watch.stop(); System.err.println("[END] Stratum " + stratumNum + ", round " + round); System.err.println("Time: " + watch.getTime() + "ms"); } private StopWatch recordDbUpdateStart() { if (!Configuration.debugRounds) { return null; } System.err.println("[START] Updating DBs"); LocalDateTime now = LocalDateTime.now(); System.err.println("Start: " + now); StopWatch watch = new StopWatch(); watch.start(); return watch; } private void recordDbUpdateEnd(StopWatch watch) { if (watch == null) { return; } watch.stop(); Configuration.printRelSizes(System.err, "DELTA SIZE", deltaDb, false); System.err.println("[END] Updating DBs"); System.err.println("Time: " + watch.getTime() + "ms"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/SemiNaiveEvaluation.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.db.IndexedFactDbBuilder; import edu.harvard.seas.pl.formulog.db.SortedIndexedFactDb; import edu.harvard.seas.pl.formulog.db.SortedIndexedFactDb.SortedIndexedFactDbBuilder; import edu.harvard.seas.pl.formulog.eval.SemiNaiveRule.DeltaSymbol; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.magic.MagicSetTransformer; import edu.harvard.seas.pl.formulog.smt.BestMatchSmtManager; import edu.harvard.seas.pl.formulog.smt.CallAndResetSolver; import edu.harvard.seas.pl.formulog.smt.CheckSatAssumingSolver; import edu.harvard.seas.pl.formulog.smt.DoubleCheckingSolver; import edu.harvard.seas.pl.formulog.smt.NotThreadSafeQueueSmtManager; import edu.harvard.seas.pl.formulog.smt.PerThreadSmtManager; import edu.harvard.seas.pl.formulog.smt.PushPopNaiveSolver; import edu.harvard.seas.pl.formulog.smt.PushPopSolver; import edu.harvard.seas.pl.formulog.smt.QueueSmtManager; import edu.harvard.seas.pl.formulog.smt.SingleShotSolver; import edu.harvard.seas.pl.formulog.smt.SmtLibSolver; import edu.harvard.seas.pl.formulog.smt.SmtStrategy; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.util.AbstractFJPTask; import edu.harvard.seas.pl.formulog.util.CountingFJP; import edu.harvard.seas.pl.formulog.util.CountingFJPImpl; import edu.harvard.seas.pl.formulog.util.MockCountingFJP; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.FunctionDefValidation; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import edu.harvard.seas.pl.formulog.validating.Stratifier; import edu.harvard.seas.pl.formulog.validating.Stratum; import edu.harvard.seas.pl.formulog.validating.ValidRule; import edu.harvard.seas.pl.formulog.validating.ast.SimpleRule; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; public class SemiNaiveEvaluation implements Evaluation { private final SortedIndexedFactDb db; private final SortedIndexedFactDb deltaDb; private final SortedIndexedFactDb nextDeltaDb; private final List strata; private final UserPredicate query; private final CountingFJP exec; private final Set trackedRelations; private final WellTypedProgram inputProgram; private final Map> rules; private final boolean eagerEval; static final boolean sequential = System.getProperty("sequential") != null; static final boolean debugRounds = Configuration.debugRounds; @SuppressWarnings("serial") public static SemiNaiveEvaluation setup(WellTypedProgram prog, int parallelism, boolean eagerEval) throws InvalidProgramException { FunctionDefValidation.validate(prog); MagicSetTransformer mst = new MagicSetTransformer(prog); BasicProgram magicProg = mst.transform(Configuration.useDemandTransformation, Configuration.restoreStratification); Set allRelations = new HashSet<>(magicProg.getFactSymbols()); allRelations.addAll(magicProg.getRuleSymbols()); allRelations.addAll(prog.getRuleSymbols()); SortedIndexedFactDbBuilder dbb = new SortedIndexedFactDbBuilder(allRelations); SortedIndexedFactDbBuilder deltaDbb = new SortedIndexedFactDbBuilder(magicProg.getRuleSymbols()); PredicateFunctionSetter predFuncs = new PredicateFunctionSetter(magicProg.getFunctionCallFactory().getDefManager(), dbb); Map> rules = new HashMap<>(); List strata = new Stratifier(magicProg).stratify(); for (Stratum stratum : strata) { if (stratum.hasRecursiveNegationOrAggregation()) { throw new InvalidProgramException( "Cannot handle recursive negation or aggregation: " + stratum); } Set stratumSymbols = stratum.getPredicateSyms(); for (RelationSymbol sym : stratumSymbols) { Set rs = new HashSet<>(); for (BasicRule br : magicProg.getRules(sym)) { for (SemiNaiveRule snr : SemiNaiveRule.make(br, stratumSymbols)) { BiFunction, Integer> score = chooseScoringFunction(eagerEval); ValidRule vr = ValidRule.make(tweakDeltaAtom(snr), score); checkRule(vr, eagerEval); predFuncs.preprocess(vr); SimpleRule sr = SimpleRule.make(vr, magicProg.getFunctionCallFactory()); IndexedRule ir = IndexedRule.make( sr, p -> { RelationSymbol psym = p.getSymbol(); if (psym instanceof DeltaSymbol) { psym = ((DeltaSymbol) psym).getBaseSymbol(); return deltaDbb.makeIndex(psym, p.getBindingPattern()); } else { return dbb.makeIndex(psym, p.getBindingPattern()); } }); rs.add(ir); if (Configuration.printFinalRules) { System.err.println("[FINAL RULE]:\n" + ir); } } } rules.put(sym, rs); } } SortedIndexedFactDb db = dbb.build(); predFuncs.setDb(db); SmtLibSolver smt = getSmtManager(); try { smt.start(magicProg); } catch (EvaluationException e) { throw new InvalidProgramException("Problem initializing SMT shims: " + e.getMessage()); } FunctionDefManager defManager = magicProg.getFunctionCallFactory().getDefManager(); defManager.loadBuiltInFunctions(smt); CountingFJP exec; if (sequential) { exec = new MockCountingFJP(); } else { exec = new CountingFJPImpl(parallelism); } for (RelationSymbol sym : magicProg.getFactSymbols()) { for (Iterable tups : Util.splitIterable(magicProg.getFacts(sym), Configuration.taskSize)) { exec.externallyAddTask( new AbstractFJPTask(exec) { @Override public void doTask() throws EvaluationException { for (Term[] tup : tups) { try { db.add(sym, Terms.normalize(tup, new SimpleSubstitution())); } catch (EvaluationException e) { UserPredicate p = UserPredicate.make(sym, tup, false); throw new EvaluationException( "Cannot normalize fact " + p + ":\n" + e.getMessage()); } } } }); } } exec.blockUntilFinished(); if (exec.hasFailed()) { exec.shutdown(); throw new InvalidProgramException(exec.getFailureCause()); } return new SemiNaiveEvaluation( prog, db, deltaDbb, rules, magicProg.getQuery(), strata, exec, getTrackedRelations(magicProg.getSymbolManager()), eagerEval); } private static Rule tweakDeltaAtom( Rule r) { List newBody = new ArrayList<>(); for (ComplexLiteral l : r) { l.accept( new ComplexLiteralVisitor() { @Override public Void visit(UnificationPredicate unificationPredicate, Void input) { newBody.add(unificationPredicate); return null; } @Override public Void visit(UserPredicate userPredicate, Void input) { RelationSymbol sym = userPredicate.getSymbol(); if (sym instanceof DeltaSymbol) { Term[] args = userPredicate.getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { Term arg = args[i]; if (arg.containsUnevaluatedTerm()) { Var x = Var.fresh(); newBody.add(UnificationPredicate.make(x, arg, false)); arg = x; } newArgs[i] = arg; } newBody.add(UserPredicate.make(sym, newArgs, false)); } else { newBody.add(userPredicate); } return null; } }, null); } return BasicRule.make(r.getHead(), newBody); } private static void checkRule(ValidRule r, boolean eagerEval) throws InvalidProgramException { if (!eagerEval) { return; } boolean seenUserPred = false; for (ComplexLiteral l : r) { if (l instanceof UserPredicate) { UserPredicate pred = (UserPredicate) l; if (seenUserPred && pred.getSymbol() instanceof DeltaSymbol) { throw new InvalidProgramException("Delta symbol could not be placed first:\n" + r); } seenUserPred = true; } } } private static SmtLibSolver maybeDoubleCheckSolver(SmtLibSolver inner) { if (Configuration.smtDoubleCheckUnknowns) { return new DoubleCheckingSolver(inner); } return inner; } private static SmtLibSolver makeNaiveSolver() { return Configuration.smtUseSingleShotSolver ? new SingleShotSolver() : new CallAndResetSolver(); } private static SmtLibSolver getSmtManager() { SmtStrategy strategy = Main.smtStrategy; switch (strategy.getTag()) { case QUEUE: { int size = (int) strategy.getMetadata(); return new QueueSmtManager( size, () -> maybeDoubleCheckSolver(new CheckSatAssumingSolver())); } case NAIVE: return maybeDoubleCheckSolver(makeNaiveSolver()); case PUSH_POP: return new PushPopSolver(); case PUSH_POP_NAIVE: return new PushPopNaiveSolver(); case BEST_MATCH: { int size = (int) strategy.getMetadata(); return maybeDoubleCheckSolver(new BestMatchSmtManager(size)); } case PER_THREAD_QUEUE: { int size = (int) strategy.getMetadata(); return new PerThreadSmtManager( () -> new NotThreadSafeQueueSmtManager( size, () -> maybeDoubleCheckSolver(new CheckSatAssumingSolver()))); } case PER_THREAD_BEST_MATCH: { int size = (int) strategy.getMetadata(); return new PerThreadSmtManager( () -> maybeDoubleCheckSolver(new BestMatchSmtManager(size))); } case PER_THREAD_PUSH_POP: { return new PerThreadSmtManager(() -> new PushPopSolver()); } case PER_THREAD_PUSH_POP_NAIVE: { return new PerThreadSmtManager(() -> new PushPopNaiveSolver()); } case PER_THREAD_NAIVE: { return new PerThreadSmtManager( () -> maybeDoubleCheckSolver( Configuration.smtUseSingleShotSolver ? new SingleShotSolver() : new CallAndResetSolver())); } default: throw new UnsupportedOperationException("Cannot support SMT strategy: " + strategy); } } static Set getTrackedRelations(SymbolManager sm) { Set s = new HashSet<>(); for (String name : Configuration.trackedRelations) { if (sm.hasName(name)) { Symbol sym = sm.lookupSymbol(name); if (sym instanceof RelationSymbol) { s.add((RelationSymbol) sm.lookupSymbol(name)); } else { System.err.println("[WARNING] Cannot track non-relation " + sym); } } else { System.err.println("[WARNING] Cannot track unrecognized relation " + name); } } return s; } static BiFunction, Integer> chooseScoringFunction(boolean eagerEval) { if (eagerEval) { return SemiNaiveEvaluation::score5; } switch (Configuration.optimizationSetting) { case 0: return SemiNaiveEvaluation::score0; case 1: return SemiNaiveEvaluation::score1; case 2: return SemiNaiveEvaluation::score2; case 3: return SemiNaiveEvaluation::score3; case 4: return SemiNaiveEvaluation::score4; case 5: return SemiNaiveEvaluation::score5; default: throw new IllegalArgumentException( "Unrecognized optimization setting: " + Configuration.optimizationSetting); } } static int score0(ComplexLiteral l, Set boundVars) { return 0; } static int score1(ComplexLiteral l, Set boundVars) { // This seems to be worse than just doing nothing. return l.accept( new ComplexLiteralVisitor() { @Override public Integer visit(UnificationPredicate unificationPredicate, Void input) { return Integer.MAX_VALUE; } @Override public Integer visit(UserPredicate pred, Void input) { if (pred.isNegated()) { return Integer.MAX_VALUE; } if (pred.getSymbol() instanceof DeltaSymbol) { return 100; } Term[] args = pred.getArgs(); if (args.length == 0) { return 150; } int bound = 0; for (int i = 0; i < args.length; ++i) { if (boundVars.containsAll(args[i].varSet())) { bound++; } } double percentBound = ((double) bound) / args.length * 100; return (int) percentBound; } }, null); } static int score2(ComplexLiteral l, Set boundVars) { return l.accept( new ComplexLiteralVisitor() { @Override public Integer visit(UnificationPredicate unificationPredicate, Void input) { return Integer.MAX_VALUE; } @Override public Integer visit(UserPredicate pred, Void input) { Term[] args = pred.getArgs(); if (args.length == 0) { return 150; } int bound = 0; for (int i = 0; i < args.length; ++i) { if (boundVars.containsAll(args[i].varSet())) { bound++; } } double percentBound = ((double) bound) / args.length * 100; return (int) percentBound; } }, null); } static int score3(ComplexLiteral l, Set boundVars) { return l.accept( new ComplexLiteralVisitor() { @Override public Integer visit(UnificationPredicate unificationPredicate, Void input) { return Integer.MAX_VALUE; } @Override public Integer visit(UserPredicate pred, Void input) { if (pred.isNegated()) { return Integer.MAX_VALUE; } if (pred.getSymbol() instanceof DeltaSymbol) { return 125; } Term[] args = pred.getArgs(); if (args.length == 0) { return Integer.MAX_VALUE; } int bound = 0; for (int i = 0; i < args.length; ++i) { if (boundVars.containsAll(args[i].varSet())) { bound++; } } double percentBound = ((double) bound) / args.length * 100; return (int) percentBound; } }, null); } static int score4(ComplexLiteral l, Set boundVars) { return l.accept( new ComplexLiteralVisitor() { @Override public Integer visit(UnificationPredicate unificationPredicate, Void input) { return Integer.MAX_VALUE; } @Override public Integer visit(UserPredicate pred, Void input) { if (pred.isNegated() || pred.getSymbol() instanceof DeltaSymbol) { return Integer.MAX_VALUE; } return 0; } }, null); } static int score5(ComplexLiteral l, Set boundVars) { return l.accept( new ComplexLiteralVisitor() { @Override public Integer visit(UnificationPredicate unificationPredicate, Void input) { return 0; } @Override public Integer visit(UserPredicate pred, Void input) { if (pred.getSymbol() instanceof DeltaSymbol) { return Integer.MAX_VALUE; } return 0; } }, null); } SemiNaiveEvaluation( WellTypedProgram inputProgram, SortedIndexedFactDb db, IndexedFactDbBuilder deltaDbb, Map> rules, UserPredicate query, List strata, CountingFJP exec, Set trackedRelations, boolean eagerEval) { this.inputProgram = inputProgram; this.db = db; this.query = query; this.strata = strata; this.exec = exec; this.trackedRelations = trackedRelations; this.deltaDb = deltaDbb.build(); this.nextDeltaDb = deltaDbb.build(); this.rules = rules; this.eagerEval = eagerEval; } @Override public WellTypedProgram getInputProgram() { return inputProgram; } @Override public synchronized void run() throws EvaluationException { if (Configuration.printRelSizes) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { Configuration.printRelSizes(System.err, "REL SIZE", db, true); } }); } if (Configuration.debugParallelism) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { System.err.println("[STEAL COUNT] " + exec.getStealCount()); } }); } if (Configuration.recordWork) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { System.err.println("[WORK] " + Configuration.work.unsafeGet()); } }); } if (Configuration.recordDetailedWork) { Runtime.getRuntime() .addShutdownHook( new Thread() { @Override public void run() { System.err.println("[WORK ITEMS] " + Configuration.workItems.unsafeGet()); System.err.println("[NEW DERIVS] " + Configuration.newDerivs.unsafeGet()); System.err.println("[DUP DERIVS] " + Configuration.dupDerivs.unsafeGet()); Configuration.workPerRule.entrySet().stream() .map(e -> new Pair<>(e.getKey(), e.getValue().unsafeGet())) .sorted((p1, p2) -> Long.compare(p2.snd(), p1.snd())) .forEach( p -> { System.err.println("[PER RULE WORK] " + p.snd() + " " + p.fst()); }); } }); } for (Stratum stratum : strata) { evaluateStratum(stratum); } } private void evaluateStratum(Stratum stratum) throws EvaluationException { List l = new ArrayList<>(); for (RelationSymbol sym : stratum.getPredicateSyms()) { l.addAll(rules.get(sym)); } if (eagerEval) { new EagerStratumEvaluator(db, l, exec, trackedRelations).evaluate(); } else { new RoundBasedStratumEvaluator( stratum.getRank(), db, deltaDb, nextDeltaDb, l, exec, trackedRelations) .evaluate(); } } @Override public synchronized EvaluationResult getResult() { return new EvaluationResult() { @Override public Iterable getAll(RelationSymbol sym) { if (!db.getSymbols().contains(sym)) { throw new IllegalArgumentException("Unrecognized relation symbol " + sym); } return () -> new FactIterator(sym, db.getAll(sym).iterator()); } @Override public Iterable getQueryAnswer() { if (query == null) { return null; } RelationSymbol querySym = query.getSymbol(); return () -> new FactIterator(querySym, db.getAll(querySym).iterator()); } @Override public Set getSymbols() { return Collections.unmodifiableSet(db.getSymbols()); } @Override public int getCount(RelationSymbol sym) { return db.countDistinct(sym); } }; } static class FactIterator implements Iterator { final RelationSymbol sym; final Iterator bindings; public FactIterator(RelationSymbol sym, Iterator bindings) { this.sym = sym; this.bindings = bindings; } @Override public boolean hasNext() { return bindings.hasNext(); } @Override public UserPredicate next() { return UserPredicate.make(sym, bindings.next(), false); } } @Override public boolean hasQuery() { return query != null; } @Override public UserPredicate getQuery() { return query; } public SortedIndexedFactDb getDb() { return db; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/SemiNaiveRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.AbstractRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.symbols.AbstractWrappedRelationSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class SemiNaiveRule extends AbstractRule { private SemiNaiveRule(UserPredicate head, List body) { super(head, body); } public static Set make( Rule rule, Set stratumSymbols) { Set rules = new HashSet<>(); for (int i = 0; i < rule.getBodySize(); ++i) { boolean canBeDelta = rule.getBody(i) .accept( new ComplexLiteralVisitor() { @Override public Boolean visit(UnificationPredicate unificationPredicate, Void input) { return false; } @Override public Boolean visit(UserPredicate userPredicate, Void input) { return stratumSymbols.contains(userPredicate.getSymbol()); } }, null); if (canBeDelta) { rules.add(make(rule, stratumSymbols, i)); } } if (rules.isEmpty()) { rules.add(new SemiNaiveRule(rule.getHead(), Util.iterableToList(rule))); } return rules; } private static SemiNaiveRule make( Rule rule, Set stratumSymbols, int deltaIdx) { List body = new ArrayList<>(); for (int i = 0; i < rule.getBodySize(); ++i) { ComplexLiteral l = rule.getBody(i); if (i == deltaIdx) { UserPredicate p = (UserPredicate) l; RelationSymbol sym = p.getSymbol(); Term[] args = p.getArgs(); l = UserPredicate.make(new DeltaSymbol(sym), args, p.isNegated()); } body.add(l); } return new SemiNaiveRule(rule.getHead(), body); } public static class DeltaSymbol extends AbstractWrappedRelationSymbol { public DeltaSymbol(RelationSymbol baseSymbol) { super(baseSymbol); assert baseSymbol.isIdbSymbol(); } @Override public String toString() { return "delta:" + getBaseSymbol(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/SmtCallFinder.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.Literal; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import java.util.HashSet; import java.util.Set; public class SmtCallFinder { private final Set smtCallSymbols = new HashSet<>(); private final Set visitedFunctions = new HashSet<>(); public SmtCallFinder() { smtCallSymbols.add(BuiltInFunctionSymbol.IS_SAT); smtCallSymbols.add(BuiltInFunctionSymbol.IS_SAT_OPT); smtCallSymbols.add(BuiltInFunctionSymbol.IS_VALID); smtCallSymbols.add(BuiltInFunctionSymbol.GET_MODEL); smtCallSymbols.add(BuiltInFunctionSymbol.IS_SET_SAT); } public boolean containsSmtCall(Literal l) { for (Term arg : l.getArgs()) { if (arg.accept(tv, null)) { return true; } } return false; } private final TermVisitor tv = new TermVisitor() { @Override public Boolean visit(Var t, Void in) { return false; } @Override public Boolean visit(Constructor c, Void in) { for (Term arg : c.getArgs()) { if (arg.accept(this, in)) { return true; } } return false; } @Override public Boolean visit(Primitive p, Void in) { return false; } @Override public Boolean visit(Expr e, Void in) { return e.accept(ev, in); } }; private final ExprVisitor ev = new ExprVisitor() { @Override public Boolean visit(MatchExpr matchExpr, Void in) { if (matchExpr.getMatchee().accept(tv, in)) { return true; } for (MatchClause match : matchExpr) { if (match.getRhs().accept(tv, in)) { return true; } } return false; } @Override public Boolean visit(FunctionCall funcCall, Void in) { FunctionSymbol sym = funcCall.getSymbol(); if (smtCallSymbols.contains(sym)) { return true; } for (Term arg : funcCall.getArgs()) { if (arg.accept(tv, in)) { return true; } } FunctionDef def = funcCall.getFactory().getDefManager().lookup(sym); if (def instanceof UserFunctionDef && visitedFunctions.add(sym) && ((UserFunctionDef) def).getBody().accept(tv, in)) { smtCallSymbols.add(sym); return true; } return false; } @Override public Boolean visit(LetFunExpr funcDef, Void in) { throw new AssertionError("impossible"); } @Override public Boolean visit(Fold fold, Void in) { return fold.getShamCall().accept(this, in); } }; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/StratumEvaluator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; public interface StratumEvaluator { void evaluate() throws EvaluationException; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/eval/UncheckedEvaluationException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; public class UncheckedEvaluationException extends RuntimeException { private static final long serialVersionUID = 3409298995077099693L; public UncheckedEvaluationException() {} public UncheckedEvaluationException(String message) { super(message); } public UncheckedEvaluationException(Throwable cause) { super(cause); } public UncheckedEvaluationException(String message, Throwable cause) { super(message, cause); } public UncheckedEvaluationException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/BuiltInFunctionDefFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.*; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.smt.SmtLibSolver; import edu.harvard.seas.pl.formulog.smt.SmtResult; import edu.harvard.seas.pl.formulog.smt.SmtStatus; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Triple; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.function.BiFunction; public final class BuiltInFunctionDefFactory { private final SmtLibSolver smt; public BuiltInFunctionDefFactory(SmtLibSolver smt) { this.smt = smt; } public FunctionDef get(BuiltInFunctionSymbol sym) { switch (sym) { case I32_ADD: return I32Add.INSTANCE; case I32_SUB: return I32Sub.INSTANCE; case I32_MUL: return I32Mul.INSTANCE; case I32_SDIV: return I32Sdiv.INSTANCE; case I32_SREM: return I32Srem.INSTANCE; case I32_UDIV: return i32Udiv; case I32_UREM: return i32Urem; case I32_NEG: return I32Neg.INSTANCE; case I32_AND: return I32And.INSTANCE; case I32_OR: return I32Or.INSTANCE; case I32_XOR: return I32Xor.INSTANCE; case I32_GT: return I32Gt.INSTANCE; case I32_GE: return I32Gte.INSTANCE; case I32_LT: return I32Lt.INSTANCE; case I32_LE: return I32Lte.INSTANCE; case I32_SHL: return i32Shl; case I32_ASHR: return i32Ashr; case I32_LSHR: return i32Lshr; case I64_SHL: return i64Shl; case I64_ASHR: return i64Ashr; case I64_LSHR: return i64Lshr; case I64_ADD: return I64Add.INSTANCE; case I64_SUB: return I64Sub.INSTANCE; case I64_MUL: return I64Mul.INSTANCE; case I64_SDIV: return I64Sdiv.INSTANCE; case I64_SREM: return I64Srem.INSTANCE; case I64_UDIV: return i64Udiv; case I64_UREM: return i64Urem; case I64_NEG: return I64Neg.INSTANCE; case I64_AND: return I64And.INSTANCE; case I64_OR: return I64Or.INSTANCE; case I64_XOR: return I64Xor.INSTANCE; case I64_GT: return I64Gt.INSTANCE; case I64_GE: return I64Gte.INSTANCE; case I64_LT: return I64Lt.INSTANCE; case I64_LE: return I64Lte.INSTANCE; case FP32_ADD: return FP32Add.INSTANCE; case FP32_SUB: return FP32Sub.INSTANCE; case FP32_MUL: return FP32Mul.INSTANCE; case FP32_DIV: return FP32Div.INSTANCE; case FP32_REM: return FP32Rem.INSTANCE; case FP32_NEG: return FP32Neg.INSTANCE; case FP32_GT: return FP32Gt.INSTANCE; case FP32_GE: return FP32Gte.INSTANCE; case FP32_LT: return FP32Lt.INSTANCE; case FP32_LE: return FP32Lte.INSTANCE; case FP32_EQ: return FP32Eq.INSTANCE; case FP64_ADD: return FP64Add.INSTANCE; case FP64_SUB: return FP64Sub.INSTANCE; case FP64_MUL: return FP64Mul.INSTANCE; case FP64_DIV: return FP64Div.INSTANCE; case FP64_REM: return FP64Rem.INSTANCE; case FP64_NEG: return FP64Neg.INSTANCE; case FP64_GT: return FP64Gt.INSTANCE; case FP64_GE: return FP64Gte.INSTANCE; case FP64_LT: return FP64Lt.INSTANCE; case FP64_LE: return FP64Lte.INSTANCE; case FP64_EQ: return FP64Eq.INSTANCE; case BEQ: return Beq.INSTANCE; case BNEQ: return Bneq.INSTANCE; case BNOT: return bnot; case TO_STRING: return ToString.INSTANCE; case STRING_CMP: return StringCmp.INSTANCE; case I32_SCMP: return I32Scmp.INSTANCE; case I32_UCMP: return I32Ucmp.INSTANCE; case I64_SCMP: return I64Scmp.INSTANCE; case I64_UCMP: return I64Ucmp.INSTANCE; case STRING_CONCAT: return StringConcat.INSTANCE; case STRING_MATCHES: return stringMatches; case STRING_STARTS_WITH: return stringStartsWith; case STRING_TO_LIST: return stringToList; case LIST_TO_STRING: return listToString; case CHAR_AT: return charAt; case SUBSTRING: return substring; case STRING_LENGTH: return stringLength; case IS_SAT: return isSat; case IS_SAT_OPT: return isSatOpt; case IS_SET_SAT: return isSetSat; case IS_VALID: return isValid; case GET_MODEL: return getModel; case QUERY_MODEL: return QueryModel.INSTANCE; case fp32ToFp64: return PrimitiveConversions.fp32ToFp64; case fp32ToI32: return PrimitiveConversions.fp32ToI32; case fp32ToI64: return PrimitiveConversions.fp32ToI64; case fp64ToFp32: return PrimitiveConversions.fp64ToFp32; case fp64ToI32: return PrimitiveConversions.fp64ToI32; case fp64ToI64: return PrimitiveConversions.fp64ToI64; case i32ToFp32: return PrimitiveConversions.i32ToFp32; case i32ToFp64: return PrimitiveConversions.i32ToFp64; case i32ToI64: return PrimitiveConversions.i32ToI64; case i64ToFp32: return PrimitiveConversions.i64ToFp32; case i64ToFp64: return PrimitiveConversions.i64ToFp64; case i64ToI32: return PrimitiveConversions.i64ToI32; case stringToI32: return PrimitiveConversions.stringToI32; case stringToI64: return PrimitiveConversions.stringToI64; case PRINT: return Print.INSTANCE; case OPAQUE_SET_CHOOSE: return OpaqueSetOps.choose; case OPAQUE_SET_DIFF: return OpaqueSetOps.diff; case OPAQUE_SET_EMPTY: return OpaqueSetOps.empty; case OPAQUE_SET_MINUS: return OpaqueSetOps.minus; case OPAQUE_SET_PLUS: return OpaqueSetOps.plus; case OPAQUE_SET_SIZE: return OpaqueSetOps.size; case OPAQUE_SET_UNION: return OpaqueSetOps.union; case OPAQUE_SET_MEMBER: return OpaqueSetOps.member; case OPAQUE_SET_SINGLETON: return OpaqueSetOps.singleton; case OPAQUE_SET_SUBSET: return OpaqueSetOps.subset; case OPAQUE_SET_FROM_LIST: return OpaqueSetOps.fromList; } throw new AssertionError(); } private enum I32Add implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_ADD; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() + arg2.getVal()); } } private enum I32Sub implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_SUB; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() - arg2.getVal()); } } private enum I32Mul implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_MUL; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() * arg2.getVal()); } } private enum I32Sdiv implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_SDIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return I32.make(arg1.getVal() / arg2.getVal()); } } private enum I32Srem implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_SREM; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return I32.make(arg1.getVal() % arg2.getVal()); } } private static final FunctionDef i32Udiv = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_UDIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return I32.make(Integer.divideUnsigned(arg1.getVal(), arg2.getVal())); } }; private static final FunctionDef i32Urem = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_UREM; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return I32.make(Integer.remainderUnsigned(arg1.getVal(), arg2.getVal())); } }; private enum I32Gt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_GT; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return boolToBoolTerm(arg1.getVal() > arg2.getVal()); } } private enum I32Gte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_GE; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return boolToBoolTerm(arg1.getVal() >= arg2.getVal()); } } private enum I32Lt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_LT; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return boolToBoolTerm(arg1.getVal() < arg2.getVal()); } } private enum I32Lte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_LE; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return boolToBoolTerm(arg1.getVal() <= arg2.getVal()); } } private enum I32And implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_AND; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() & arg2.getVal()); } } private enum I32Or implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_OR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() | arg2.getVal()); } } private enum I32Xor implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_XOR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() ^ arg2.getVal()); } } private enum I32Neg implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_NEG; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; return I32.make(-arg1.getVal()); } } private static final FunctionDef i32Shl = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_SHL; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() << arg2.getVal()); } }; private static final FunctionDef i32Ashr = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_ASHR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() >> arg2.getVal()); } }; private static final FunctionDef i32Lshr = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_LSHR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 arg1 = (I32) args[0]; I32 arg2 = (I32) args[1]; return I32.make(arg1.getVal() >>> arg2.getVal()); } }; private static final FunctionDef i64Shl = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_SHL; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() << arg2.getVal()); } }; private static final FunctionDef i64Ashr = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_ASHR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() >> arg2.getVal()); } }; private static final FunctionDef i64Lshr = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_LSHR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() >>> arg2.getVal()); } }; private enum I64Add implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_ADD; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() + arg2.getVal()); } } private enum I64Sub implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_SUB; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() - arg2.getVal()); } } private enum I64Mul implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_MUL; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() * arg2.getVal()); } } private enum I64Sdiv implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_SDIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return I64.make(arg1.getVal() / arg2.getVal()); } } private enum I64Srem implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_SREM; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return I64.make(arg1.getVal() % arg2.getVal()); } } private static final FunctionDef i64Udiv = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_UDIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return I64.make(Long.divideUnsigned(arg1.getVal(), arg2.getVal())); } }; private static final FunctionDef i64Urem = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_UREM; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return I64.make(Long.remainderUnsigned(arg1.getVal(), arg2.getVal())); } }; private enum I64Gt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_GT; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return boolToBoolTerm(arg1.getVal() > arg2.getVal()); } } private enum I64Gte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_GE; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return boolToBoolTerm(arg1.getVal() >= arg2.getVal()); } } private enum I64Lt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_LT; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return boolToBoolTerm(arg1.getVal() < arg2.getVal()); } } private enum I64Lte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_LE; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return boolToBoolTerm(arg1.getVal() <= arg2.getVal()); } } private enum I64And implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_AND; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() & arg2.getVal()); } } private enum I64Or implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_OR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() | arg2.getVal()); } } private enum I64Xor implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_XOR; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; I64 arg2 = (I64) args[1]; return I64.make(arg1.getVal() ^ arg2.getVal()); } } private enum I64Neg implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_NEG; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 arg1 = (I64) args[0]; return I64.make(-arg1.getVal()); } } private enum FP32Add implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_ADD; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return FP32.make(arg1.getVal() + arg2.getVal()); } } private enum FP32Sub implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_SUB; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return FP32.make(arg1.getVal() - arg2.getVal()); } } private enum FP32Mul implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_MUL; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return FP32.make(arg1.getVal() * arg2.getVal()); } } private enum FP32Div implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_DIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return FP32.make(arg1.getVal() / arg2.getVal()); } } private enum FP32Rem implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_REM; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return FP32.make(arg1.getVal() % arg2.getVal()); } } private enum FP32Gt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_GT; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return boolToBoolTerm(arg1.getVal() > arg2.getVal()); } } private enum FP32Gte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_GE; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return boolToBoolTerm(arg1.getVal() >= arg2.getVal()); } } private enum FP32Lt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_LT; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return boolToBoolTerm(arg1.getVal() < arg2.getVal()); } } private enum FP32Lte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_LE; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return boolToBoolTerm(arg1.getVal() <= arg2.getVal()); } } private enum FP32Eq implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_EQ; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; FP32 arg2 = (FP32) args[1]; return boolToBoolTerm(arg1.getVal().floatValue() == arg2.getVal().floatValue()); } } private enum FP32Neg implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP32_NEG; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 arg1 = (FP32) args[0]; return FP32.make(-arg1.getVal()); } } private enum FP64Add implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_ADD; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return FP64.make(arg1.getVal() + arg2.getVal()); } } private enum FP64Sub implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_SUB; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return FP64.make(arg1.getVal() - arg2.getVal()); } } private enum FP64Mul implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_MUL; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return FP64.make(arg1.getVal() * arg2.getVal()); } } private enum FP64Div implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_DIV; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Division by zero"); } return FP64.make(arg1.getVal() / arg2.getVal()); } } private enum FP64Rem implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_REM; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; if (arg2.getVal() == 0) { throw new EvaluationException("Remainder by zero"); } return FP64.make(arg1.getVal() % arg2.getVal()); } } private enum FP64Gt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_GT; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return boolToBoolTerm(arg1.getVal() > arg2.getVal()); } } private enum FP64Gte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_GE; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return boolToBoolTerm(arg1.getVal() >= arg2.getVal()); } } private enum FP64Lt implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_LT; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return boolToBoolTerm(arg1.getVal() < arg2.getVal()); } } private enum FP64Lte implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_LE; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return boolToBoolTerm(arg1.getVal() <= arg2.getVal()); } } private enum FP64Eq implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_EQ; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; FP64 arg2 = (FP64) args[1]; return boolToBoolTerm(arg1.getVal().doubleValue() == arg2.getVal().doubleValue()); } } private enum FP64Neg implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.FP64_NEG; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 arg1 = (FP64) args[0]; return FP64.make(-arg1.getVal()); } } private enum Beq implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.BEQ; } @Override public Term evaluate(Term[] args) throws EvaluationException { Term arg1 = args[0]; Term arg2 = args[1]; return boolToBoolTerm(arg1.equals(arg2)); } } private enum Bneq implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.BNEQ; } @Override public Term evaluate(Term[] args) throws EvaluationException { Term arg1 = args[0]; Term arg2 = args[1]; return boolToBoolTerm(!arg1.equals(arg2)); } } private static final FunctionDef bnot = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.BNOT; } @Override public Term evaluate(Term[] args) throws EvaluationException { Term arg = args[0]; if (arg.equals(trueTerm)) { return falseTerm; } return trueTerm; } }; private enum ToString implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.TO_STRING; } @Override public Term evaluate(Term[] args) throws EvaluationException { Term arg = args[0]; if (arg instanceof StringTerm) { return arg; } return StringTerm.make(args[0].toString()); } } private enum StringCmp implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_CMP; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s1 = ((StringTerm) args[0]).getVal(); String s2 = ((StringTerm) args[1]).getVal(); return makeCmp(s1, s2, (x, y) -> x.compareTo(y)); } } private enum I32Scmp implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_SCMP; } @Override public Term evaluate(Term[] args) throws EvaluationException { int x = ((I32) args[0]).getVal(); int y = ((I32) args[1]).getVal(); return makeCmp(x, y, Integer::compare); } } private enum I32Ucmp implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I32_UCMP; } @Override public Term evaluate(Term[] args) throws EvaluationException { int x = ((I32) args[0]).getVal(); int y = ((I32) args[1]).getVal(); return makeCmp(x, y, Integer::compareUnsigned); } } private enum I64Scmp implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_SCMP; } @Override public Term evaluate(Term[] args) throws EvaluationException { long x = ((I64) args[0]).getVal(); long y = ((I64) args[1]).getVal(); return makeCmp(x, y, Long::compare); } } private enum I64Ucmp implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.I64_UCMP; } @Override public Term evaluate(Term[] args) throws EvaluationException { long x = ((I64) args[0]).getVal(); long y = ((I64) args[1]).getVal(); return makeCmp(x, y, Long::compareUnsigned); } } private enum StringConcat implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_CONCAT; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s1 = ((StringTerm) args[0]).getVal(); String s2 = ((StringTerm) args[1]).getVal(); return StringTerm.make(s1 + s2); } } private static final FunctionDef stringMatches = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_MATCHES; } @Override public Term evaluate(Term[] args) throws EvaluationException { String str = ((StringTerm) args[0]).getVal(); String re = ((StringTerm) args[1]).getVal(); return boolToBoolTerm(str.matches(re)); } }; private static final FunctionDef stringStartsWith = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_STARTS_WITH; } @Override public Term evaluate(Term[] args) throws EvaluationException { String str = ((StringTerm) args[0]).getVal(); String pre = ((StringTerm) args[1]).getVal(); return boolToBoolTerm(str.startsWith(pre)); } }; private static final FunctionDef stringToList = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_TO_LIST; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s = ((StringTerm) args[0]).getVal(); List l = new ArrayList<>(); for (int i = 0; i < s.length(); ++i) { l.add(I32.make(s.charAt(i))); } return Terms.termListToTerm(l); } }; private static final FunctionDef listToString = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.LIST_TO_STRING; } @Override public Term evaluate(Term[] args) throws EvaluationException { List l = Terms.termToTermList(args[0]); char[] cs = new char[l.size()]; int i = 0; for (I32 c : l) { cs[i] = (char) c.getVal().intValue(); i++; } return StringTerm.make(new String(cs)); } }; private static final FunctionDef charAt = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.CHAR_AT; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s = ((StringTerm) args[0]).getVal(); int i = ((I32) args[1]).getVal(); if (i < 0 || i >= s.length()) { return Constructors.none(); } return Constructors.some(I32.make(s.charAt(i))); } }; private static final FunctionDef substring = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.SUBSTRING; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s = ((StringTerm) args[0]).getVal(); int i = ((I32) args[1]).getVal(); int j = ((I32) args[2]).getVal(); if (i < 0 || j > s.length() || i > j) { return Constructors.none(); } return Constructors.some(StringTerm.make(s.substring(i, j))); } }; private static final FunctionDef stringLength = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.STRING_LENGTH; } @Override public Term evaluate(Term[] args) throws EvaluationException { String s = ((StringTerm) args[0]).getVal(); return I32.make(s.length()); } }; private final Map, Boolean, Integer>, Future> smtMemo = new ConcurrentHashMap<>(); private Pair querySmt(SmtLibTerm assertions, boolean getModel) throws EvaluationException { return querySmt(assertions, getModel, Integer.MAX_VALUE); } private Pair querySmt(SmtLibTerm assertions, boolean getModel, int timeout) throws EvaluationException { return querySmt(breakIntoConjuncts(assertions), getModel, timeout); } private List breakIntoConjuncts(SmtLibTerm assertion) { List l = new ArrayList<>(); breakIntoConjuncts(assertion, l); return l; } private void breakIntoConjunctsNegated(SmtLibTerm assertion, List acc) { if (assertion instanceof Constructor) { Constructor c = (Constructor) assertion; ConstructorSymbol sym = c.getSymbol(); Term[] args = c.getArgs(); if (sym.equals(BuiltInConstructorSymbol.SMT_NOT)) { // Turn ~~A into A breakIntoConjuncts((SmtLibTerm) args[0], acc); return; } else if (sym.equals(BuiltInConstructorSymbol.SMT_IMP)) { // Turn ~(A => B) into A /\ ~B breakIntoConjuncts((SmtLibTerm) args[0], acc); breakIntoConjunctsNegated((SmtLibTerm) args[1], acc); return; } else if (sym.equals(BuiltInConstructorSymbol.SMT_OR)) { // Turn ~(A \/ B) into ~A /\ ~B breakIntoConjunctsNegated((SmtLibTerm) args[0], acc); breakIntoConjunctsNegated((SmtLibTerm) args[1], acc); return; } } else if (assertion.equals(trueTerm)) { // Turn ~True into False acc.add(falseTerm); return; } if (!assertion.equals(falseTerm)) { acc.add(negate(assertion)); } } private void breakIntoConjuncts(SmtLibTerm assertion, List acc) { if (assertion instanceof Constructor) { Constructor c = (Constructor) assertion; ConstructorSymbol sym = c.getSymbol(); Term[] args = c.getArgs(); if (sym.equals(BuiltInConstructorSymbol.SMT_AND)) { breakIntoConjuncts((SmtLibTerm) args[0], acc); breakIntoConjuncts((SmtLibTerm) args[1], acc); return; } else if (sym.equals(BuiltInConstructorSymbol.SMT_NOT)) { breakIntoConjunctsNegated((SmtLibTerm) args[0], acc); return; } } if (!assertion.equals(trueTerm)) { acc.add(assertion); } } private SmtLibTerm negate(Term t) { return Constructors.make(BuiltInConstructorSymbol.SMT_NOT, Terms.singletonArray(t)); } private Pair querySmt( Collection assertions, boolean getModel, int timeout) throws EvaluationException { long start = System.nanoTime(); try { if (timeout < 0) { timeout = -1; } Set set = new LinkedHashSet<>(assertions); if (set.contains(BoolTerm.mkFalse())) { return new Pair<>(SmtStatus.UNSATISFIABLE, null); } set.remove(BoolTerm.mkTrue()); if (set.isEmpty()) { Model m = getModel ? Model.make(Collections.emptyMap()) : null; return new Pair<>(SmtStatus.SATISFIABLE, m); } SmtResult res; if (Configuration.smtMemoize) { res = querySmtWithMemo(set, getModel, timeout); } else { res = smt.check(set, getModel, timeout); } return new Pair<>(res.status, res.model); } finally { Configuration.recordSmtTime(System.nanoTime() - start); } } private SmtResult querySmtWithMemo(Set assertions, boolean getModel, int timeout) throws EvaluationException { Triple, Boolean, Integer> key = new Triple<>(assertions, getModel, timeout); CompletableFuture completableFut = new CompletableFuture<>(); Future fut = smtMemo.putIfAbsent(key, completableFut); if (fut == null) { completableFut.complete(smt.check(assertions, getModel, timeout)); fut = completableFut; } long waitStart = 0; if (Configuration.timeSmt || Main.smtStats) { waitStart = System.nanoTime(); } try { return fut.get(); } catch (InterruptedException | ExecutionException e) { throw new EvaluationException(e); } finally { if (Configuration.timeSmt || Main.smtStats) { Configuration.recordSmtWaitTime(System.nanoTime() - waitStart); } } } private final FunctionDef isSat = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.IS_SAT; } @Override public Term evaluate(Term[] args) throws EvaluationException { SmtLibTerm formula = (SmtLibTerm) args[0]; Pair p = querySmt(formula, false); switch (p.fst()) { case SATISFIABLE: return trueTerm; case UNKNOWN: throw new EvaluationException("Z3 returned \"unknown\""); case UNSATISFIABLE: return falseTerm; } throw new AssertionError("impossible"); } }; private final FunctionDef isSatOpt = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.IS_SAT_OPT; } @Override public Term evaluate(Term[] args) throws EvaluationException { SmtLibTerm formula = (SmtLibTerm) args[0]; List assertions = Terms.termToTermList(formula); Collections.reverse(assertions); Constructor timeoutOpt = (Constructor) args[1]; Integer timeout = extractOptionalTimeout(timeoutOpt); Pair p = querySmt(assertions, false, timeout); switch (p.fst()) { case SATISFIABLE: return Constructors.some(trueTerm); case UNKNOWN: return Constructors.none(); case UNSATISFIABLE: return Constructors.some(falseTerm); } throw new AssertionError("impossible"); } }; private final FunctionDef isSetSat = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.IS_SET_SAT; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet formula = (OpaqueSet) args[0]; Constructor timeoutOpt = (Constructor) args[1]; Integer timeout = extractOptionalTimeout(timeoutOpt); @SuppressWarnings({"unchecked", "rawtypes"}) Pair p = querySmt((Collection) formula.getCollection(), false, timeout); switch (p.fst()) { case SATISFIABLE: return Constructors.some(trueTerm); case UNKNOWN: return Constructors.none(); case UNSATISFIABLE: return Constructors.some(falseTerm); } throw new AssertionError("impossible"); } }; private final FunctionDef isValid = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.IS_VALID; } @Override public Term evaluate(Term[] args) throws EvaluationException { SmtLibTerm formula = negate((SmtLibTerm) args[0]); Pair p = querySmt(formula, false); switch (p.fst()) { case SATISFIABLE: return falseTerm; case UNKNOWN: throw new EvaluationException("Z3 returned \"unknown\""); case UNSATISFIABLE: return trueTerm; } throw new AssertionError("impossible"); } }; private static int extractOptionalTimeout(Constructor opt) { if (opt.getSymbol().equals(BuiltInConstructorSymbol.SOME)) { return ((I32) opt.getArgs()[0]).getVal(); } return Integer.MAX_VALUE; } private final FunctionDef getModel = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.GET_MODEL; } @Override public Term evaluate(Term[] args) throws EvaluationException { List assertions = Terms.termToTermList((SmtLibTerm) args[0]); Collections.reverse(assertions); Constructor timeoutOpt = (Constructor) args[1]; Integer timeout = extractOptionalTimeout(timeoutOpt); Pair p = querySmt(assertions, true, timeout); Model model = p.snd(); return model == null ? Constructors.none() : Constructors.some(model); } }; private enum QueryModel implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.QUERY_MODEL; } @Override public Term evaluate(Term[] args) throws EvaluationException { SolverVariable x = (SolverVariable) args[0]; Model m = (Model) args[1]; Term t = m.getVal().get(x); return t == null ? Constructors.none() : Constructors.some(t); } } private static final SmtLibTerm trueTerm = BoolTerm.mkTrue(); private static final SmtLibTerm falseTerm = BoolTerm.mkFalse(); private static Term boolToBoolTerm(boolean b) { if (b) { return trueTerm; } else { return falseTerm; } } private enum Print implements FunctionDef { INSTANCE; @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.PRINT; } @Override public Term evaluate(Term[] args) throws EvaluationException { System.out.println(args[0]); return trueTerm; } } private static Term makeCmp(T x, T y, BiFunction cmp) { int z = cmp.apply(x, y); if (z < 0) { return Constructors.makeZeroAry(BuiltInConstructorSymbol.CMP_LT); } else if (z > 0) { return Constructors.makeZeroAry(BuiltInConstructorSymbol.CMP_GT); } return Constructors.makeZeroAry(BuiltInConstructorSymbol.CMP_EQ); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/DummyFunctionDef.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; public class DummyFunctionDef implements FunctionDef { private final FunctionSymbol sym; private volatile FunctionDef def; public DummyFunctionDef(FunctionSymbol sym) { this.sym = sym; } @Override public FunctionSymbol getSymbol() { return sym; } @Override public Term evaluate(Term[] args) throws EvaluationException { if (def == null) { throw new EvaluationException(); } return def.evaluate(args); } public void setDef(FunctionDef def) { this.def = def; } public Object getDef() { return def; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/FunctionDef.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; public interface FunctionDef { FunctionSymbol getSymbol(); Term evaluate(Term[] args) throws EvaluationException; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/FunctionDefManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.smt.SmtLibSolver; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class FunctionDefManager { private final Map memo = new HashMap<>(); public FunctionDefManager() { for (BuiltInFunctionSymbol sym : BuiltInFunctionSymbol.values()) { memo.put(sym, new DummyFunctionDef(sym)); } } public void register(FunctionDef def) { if (memo.put(def.getSymbol(), def) != null) { throw new IllegalArgumentException( "Cannot register multiple definitions for the same function: " + def.getSymbol()); } } public void reregister(FunctionDef def) { if (memo.put(def.getSymbol(), def) == null) { throw new IllegalArgumentException( "Expected there to already be a definition for function " + def.getSymbol()); } } public FunctionDef lookup(FunctionSymbol symbol) { FunctionDef def = memo.get(symbol); if (def == null) { throw new IllegalArgumentException("No function defined for symbol: " + symbol); } return def; } public boolean hasDefinition(FunctionSymbol sym) { return memo.containsKey(sym); } public Set getFunctionSymbols() { return Collections.unmodifiableSet(new HashSet<>(memo.keySet())); } public void loadBuiltInFunctions(SmtLibSolver smt) { BuiltInFunctionDefFactory builtIns = new BuiltInFunctionDefFactory(smt); for (BuiltInFunctionSymbol sym : BuiltInFunctionSymbol.values()) { memo.put(sym, builtIns.get(sym)); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/OpaqueSetOps.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2021-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.BoolTerm; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.I32; import edu.harvard.seas.pl.formulog.ast.OpaqueSet; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.util.Pair; public final class OpaqueSetOps { private OpaqueSetOps() { throw new AssertionError("impossible"); } public static final FunctionDef empty = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_EMPTY; } @Override public Term evaluate(Term[] args) throws EvaluationException { return OpaqueSet.empty(); } }; public static final FunctionDef size = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_SIZE; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s = (OpaqueSet) args[0]; return I32.make(s.size()); } }; public static final FunctionDef plus = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_PLUS; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s = (OpaqueSet) args[1]; return s.plus(args[0]); } }; public static final FunctionDef minus = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_MINUS; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s = (OpaqueSet) args[1]; return s.minus(args[0]); } }; public static final FunctionDef union = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_UNION; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s1 = (OpaqueSet) args[0]; OpaqueSet s2 = (OpaqueSet) args[1]; return s1.union(s2); } }; public static final FunctionDef diff = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_DIFF; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s1 = (OpaqueSet) args[0]; OpaqueSet s2 = (OpaqueSet) args[1]; return s1.diff(s2); } }; public static final FunctionDef choose = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_CHOOSE; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s = (OpaqueSet) args[0]; Pair p = s.choose(); if (p == null) { return Constructors.none(); } return Constructors.some(Constructors.tuple(p.fst(), p.snd())); } }; public static final FunctionDef member = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_MEMBER; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s = (OpaqueSet) args[1]; return BoolTerm.mk(s.member(args[0])); } }; public static final FunctionDef singleton = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_SINGLETON; } @Override public Term evaluate(Term[] args) throws EvaluationException { return OpaqueSet.singleton(args[0]); } }; public static final FunctionDef subset = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_SUBSET; } @Override public Term evaluate(Term[] args) throws EvaluationException { OpaqueSet s1 = (OpaqueSet) args[0]; OpaqueSet s2 = (OpaqueSet) args[1]; return BoolTerm.mk(s2.containsAll(s1)); } }; public static final FunctionDef fromList = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.OPAQUE_SET_SUBSET; } @Override public Term evaluate(Term[] args) throws EvaluationException { return OpaqueSet.fromCollection(Terms.termToTermList(args[0])); } }; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/PredicateFunctionDef.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.BindingType; public interface PredicateFunctionDef extends FunctionDef { int getIndex(); BindingType[] getBindingsForIndex(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/PrimitiveConversions.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.FP32; import edu.harvard.seas.pl.formulog.ast.FP64; import edu.harvard.seas.pl.formulog.ast.I32; import edu.harvard.seas.pl.formulog.ast.I64; import edu.harvard.seas.pl.formulog.ast.StringTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class PrimitiveConversions { private PrimitiveConversions() { throw new AssertionError(); } public static final FunctionDef i32ToI64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i32ToI64; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 x = (I32) args[0]; return I64.make(x.getVal()); } }; public static final FunctionDef i32ToFp32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i32ToFp32; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 x = (I32) args[0]; return FP32.make(x.getVal()); } }; public static final FunctionDef i32ToFp64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i32ToFp64; } @Override public Term evaluate(Term[] args) throws EvaluationException { I32 x = (I32) args[0]; return FP64.make(x.getVal()); } }; public static final FunctionDef i64ToI32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i64ToI32; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 x = (I64) args[0]; return I32.make(x.getVal().intValue()); } }; public static final FunctionDef i64ToFp32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i64ToFp32; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 x = (I64) args[0]; return FP32.make(x.getVal()); } }; public static final FunctionDef i64ToFp64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.i64ToFp64; } @Override public Term evaluate(Term[] args) throws EvaluationException { I64 x = (I64) args[0]; return FP64.make(x.getVal()); } }; public static final FunctionDef fp32ToI32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp32ToI32; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 x = (FP32) args[0]; return I32.make(x.getVal().intValue()); } }; public static final FunctionDef fp32ToI64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp32ToI64; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 x = (FP32) args[0]; return I64.make(x.getVal().longValue()); } }; public static final FunctionDef fp32ToFp64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp32ToFp64; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP32 x = (FP32) args[0]; return FP64.make(x.getVal()); } }; public static final FunctionDef fp64ToI32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp64ToI32; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 x = (FP64) args[0]; return I32.make(x.getVal().intValue()); } }; public static final FunctionDef fp64ToI64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp64ToI64; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 x = (FP64) args[0]; return I64.make(x.getVal().longValue()); } }; public static final FunctionDef fp64ToFp32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.fp64ToFp32; } @Override public Term evaluate(Term[] args) throws EvaluationException { FP64 x = (FP64) args[0]; return FP32.make(x.getVal().floatValue()); } }; private static final Pattern hex = Pattern.compile("0x([0-9a-fA-F]+)"); public static final FunctionDef stringToI32 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.stringToI32; } @Override public Term evaluate(Term[] args) throws EvaluationException { try { String s = ((StringTerm) args[0]).getVal(); Matcher m = hex.matcher(s); Integer i; if (m.matches()) { i = Integer.parseUnsignedInt(m.group(1), 16); } else { i = Integer.parseInt(s); } return Constructors.some(I32.make(i)); } catch (NumberFormatException e) { return Constructors.none(); } } }; public static final FunctionDef stringToI64 = new FunctionDef() { @Override public FunctionSymbol getSymbol() { return BuiltInFunctionSymbol.stringToI64; } @Override public Term evaluate(Term[] args) throws EvaluationException { try { String s = ((StringTerm) args[0]).getVal(); Matcher m = hex.matcher(s); Long i; if (m.matches()) { i = Long.parseUnsignedLong(m.group(1), 16); } else { i = Long.parseLong(s); } return Constructors.some(I64.make(i)); } catch (NumberFormatException e) { return Constructors.none(); } } }; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/RecordAccessor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; public class RecordAccessor implements FunctionDef { private final FunctionSymbol sym; private final int index; public RecordAccessor(FunctionSymbol sym, int index) { this.sym = sym; this.index = index; } @Override public FunctionSymbol getSymbol() { return sym; } public int getIndex() { return index; } @Override public Term evaluate(Term[] args) throws EvaluationException { Constructor ctor = (Constructor) args[0]; return ctor.getArgs()[index]; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/functions/UserFunctionDef.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.functions; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.List; public class UserFunctionDef implements FunctionDef { private final FunctionSymbol sym; private final List params; private Term body; private UserFunctionDef(FunctionSymbol sym, List params, Term body) { this.sym = sym; this.params = params; this.body = body; } public List getParams() { return params; } public Term getBody() { return body; } public void setBody(Term newBody) { this.body = newBody; } @Override public FunctionSymbol getSymbol() { return sym; } @Override public Term evaluate(Term[] args) throws EvaluationException { Substitution s = new SimpleSubstitution(); assert params.size() == args.length; int i = 0; for (Var param : params) { s.put(param, args[i]); i++; } try { return body.normalize(s); } catch (EvaluationException e) { throw new EvaluationException("Error evaluating function " + sym + ":\n" + e.getMessage()); } } public static UserFunctionDef get(FunctionSymbol sym, List params, Term body) { return new UserFunctionDef(sym, params, body); } @Override public String toString() { return "UserFunctionDef [sym=" + sym + ", params=" + params + ", body=" + body + "]"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/magic/AdornedSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.WrappedRelationSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import java.util.Arrays; class AdornedSymbol implements WrappedRelationSymbol { private final RelationSymbol baseSymbol; private final boolean[] adornment; public AdornedSymbol(RelationSymbol baseSymbol, boolean[] adornment) { assert adornment != null; assert !(baseSymbol instanceof AdornedSymbol); assert baseSymbol.isIdbSymbol(); this.baseSymbol = baseSymbol; this.adornment = adornment; } public boolean[] getAdornment() { return adornment; } @Override public String toString() { String s = getBaseSymbol() + "_"; for (boolean b : adornment) { s += b ? "b" : "f"; } return s; } @Override public boolean isIdbSymbol() { return baseSymbol.isIdbSymbol(); } @Override public boolean isBottomUp() { return baseSymbol.isBottomUp(); } @Override public boolean isTopDown() { return baseSymbol.isTopDown(); } @Override public FunctorType getCompileTimeType() { return baseSymbol.getCompileTimeType(); } @Override public int getArity() { return baseSymbol.getArity(); } @Override public RelationSymbol getBaseSymbol() { return baseSymbol; } @Override public boolean isDisk() { return false; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(adornment); result = prime * result + ((baseSymbol == null) ? 0 : baseSymbol.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AdornedSymbol other = (AdornedSymbol) obj; if (!Arrays.equals(adornment, other.adornment)) return false; if (baseSymbol == null) { if (other.baseSymbol != null) return false; } else if (!baseSymbol.equals(other.baseSymbol)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/magic/Adornments.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.Unification; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public final class Adornments { private Adornments() { throw new AssertionError(); } public static UserPredicate adorn(UserPredicate a, Set boundVars, boolean topDownIsDefault) { RelationSymbol origSym = a.getSymbol(); if (!topDownIsDefault && !origSym.isTopDown()) { return a; } boolean defaultAdornment = !origSym.isBottomUp(); Term[] args = a.getArgs(); boolean[] adornment = new boolean[args.length]; for (int k = 0; k < args.length; k++) { adornment[k] = defaultAdornment && boundVars.containsAll(args[k].varSet()); } AdornedSymbol sym = new AdornedSymbol(origSym, adornment); return UserPredicate.make(sym, args, a.isNegated()); } public static BasicRule adornRule( UserPredicate head, List body, boolean topDownIsDefault) throws InvalidProgramException { RelationSymbol sym = head.getSymbol(); boolean[] headAdornment; if (sym instanceof AdornedSymbol) { headAdornment = ((AdornedSymbol) head.getSymbol()).getAdornment(); } else { headAdornment = new boolean[sym.getArity()]; for (int i = 0; i < headAdornment.length; ++i) { headAdornment[i] = false; } } Set boundVars = new HashSet<>(); Term[] headArgs = head.getArgs(); for (int i = 0; i < headArgs.length; i++) { if (headAdornment[i]) { boundVars.addAll(headArgs[i].varSet()); } } Map varCounts = BasicRule.make(head, body).countVariables(); List newBody = new ArrayList<>(); for (ComplexLiteral lit : body) { ComplexLiteral newLit = lit.accept( new ComplexLiteralVisitor() { @Override public ComplexLiteral visit(UnificationPredicate pred, Void input) { return pred; } @Override public ComplexLiteral visit(UserPredicate pred, Void input) { if (pred.getSymbol().isIdbSymbol()) { pred = adorn(pred, boundVars, topDownIsDefault); } return pred; } }, null); if (!Unification.canBindVars(newLit, boundVars, varCounts)) { throw new InvalidProgramException( "Rule cannot be evaluated given the supplied order.\n" + "The problematic rule is:\n" + BasicRule.make(head, body) + "\nThe problematic literal is: " + lit); } boundVars.addAll(newLit.varSet()); newBody.add(newLit); } return BasicRule.make(head, newBody); // List newBody = new ArrayList<>(body); // for (int i = 0; i < newBody.size(); i++) { // boolean ok = false; // for (int j = i; j < newBody.size(); j++) { // ComplexLiteral a = newBody.get(j); // if (Unification.canBindVars(a, boundVars, varCounts)) { // Collections.swap(newBody, i, j); // int pos = i; // a.accept(new ComplexLiteralVisitor() { // // @Override // public Void visit(UnificationPredicate unificationPredicate, Void input) { // return null; // } // // @Override // public Void visit(UserPredicate userPredicate, Void input) { // if (userPredicate.getSymbol().isIdbSymbol()) { // newBody.set(pos, adorn(userPredicate, boundVars, topDownIsDefault)); // } // return null; // } // // }, null); // boundVars.addAll(a.varSet()); // ok = true; // break; // } // } // if (!ok) { // throw new InvalidProgramException( // "Cannot reorder rule to meet well-modeness restrictions: " + // BasicRule.make(head, body)); // } // } // return BasicRule.make(head, newBody); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/magic/MagicSetTransformer.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.AbstractWrappedRelationSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.PredicateFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.SymbolComparator; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.util.DedupWorkList; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import edu.harvard.seas.pl.formulog.validating.Stratifier; import edu.harvard.seas.pl.formulog.validating.Stratum; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class MagicSetTransformer { private final Program origProg; private boolean topDownIsDefault; private static final boolean debug = Configuration.debugMst; public MagicSetTransformer(Program prog) { this.origProg = prog; } public BasicProgram transform(boolean useDemandTransformation, boolean restoreStratification) throws InvalidProgramException { BasicProgram prog; if (origProg.hasQuery()) { prog = transformForQuery(origProg.getQuery(), useDemandTransformation, restoreStratification); } else { prog = transformNoQuery(useDemandTransformation, restoreStratification); } if (debug) { System.err.println("Rewritten rules..."); List syms = prog.getRuleSymbols().stream() .sorted(SymbolComparator.INSTANCE) .collect(Collectors.toList()); for (RelationSymbol sym : syms) { for (BasicRule r : prog.getRules(sym)) { System.err.println(r); } } } return prog; } private BasicProgram transformForQuery( UserPredicate query, boolean useDemandTransformation, boolean restoreStratification) throws InvalidProgramException { topDownIsDefault = true; if (query.isNegated()) { throw new InvalidProgramException("Query cannot be negated"); } BasicProgram newProg; if (query.getSymbol().isEdbSymbol()) { newProg = makeEdbProgram(query); } else { UserPredicate adornedQuery = Adornments.adorn(query, Collections.emptySet(), topDownIsDefault); Set> adRules = adorn(Collections.singleton(adornedQuery.getSymbol())); Set magicRules = makeMagicRules(adRules); magicRules.add(makeSeedRule(adornedQuery)); BasicRule queryRule = makeQueryRule(adornedQuery); UserPredicate newQuery = queryRule.getHead(); BasicProgram magicProg = new ProgramImpl(magicRules, newQuery); if (restoreStratification && !isStratified(magicProg)) { magicProg = stratify(magicProg, Pair.map(adRules, (rule, num) -> rule)); } ((ProgramImpl) magicProg).rules.put(newQuery.getSymbol(), Collections.singleton(queryRule)); if (useDemandTransformation) { magicProg = applyDemandTransformation(magicProg, restoreStratification); } newProg = magicProg; } return newProg; } private BasicRule makeSeedRule(UserPredicate adornedQuery) { return BasicRule.make(createInputAtom(adornedQuery)); } private BasicRule makeQueryRule(UserPredicate query) { RelationSymbol oldSym = query.getSymbol(); RelationSymbol querySym = new RelationSymbol() { @Override public FunctorType getCompileTimeType() { return oldSym.getCompileTimeType(); } @Override public int getArity() { return oldSym.getArity(); } @Override public boolean isIdbSymbol() { return true; } @Override public boolean isBottomUp() { return true; } @Override public boolean isTopDown() { return false; } @Override public boolean isDisk() { return false; } @Override public String toString() { Symbol sym = oldSym; if (oldSym instanceof AdornedSymbol) { sym = ((AdornedSymbol) oldSym).getBaseSymbol(); } return "query:" + sym; } }; Term[] args = query.getArgs(); Term[] newArgs = new Term[args.length]; Term[] hdArgs = new Term[args.length]; List body = new ArrayList<>(); Set seen = new HashSet<>(); for (int i = 0; i < args.length; ++i) { Term t = args[i]; if (!(t instanceof Var) || !seen.add((Var) t)) { Var x = Var.fresh(); body.add(UnificationPredicate.make(x, t, false)); t = x; } newArgs[i] = hdArgs[i] = t; } body.add(0, UserPredicate.make(oldSym, newArgs, query.isNegated())); UserPredicate hd = UserPredicate.make(querySym, hdArgs, query.isNegated()); return BasicRule.make(hd, body); } private BasicProgram makeEdbProgram(UserPredicate query) { BasicRule queryRule = makeQueryRule(query); RelationSymbol oldQuerySym = query.getSymbol(); return new BasicProgram() { @Override public Set getFunctionSymbols() { return Collections.emptySet(); } @Override public Set getFactSymbols() { return Collections.singleton(oldQuerySym); } @Override public Set getRuleSymbols() { return Collections.singleton(getQuery().getSymbol()); } @Override public FunctionDef getDef(FunctionSymbol sym) { throw new IllegalArgumentException("No definition for function with symbol: " + sym); } @Override public Set getFacts(RelationSymbol sym) { if (oldQuerySym.equals(sym)) { return origProg.getFacts(oldQuerySym); } return Collections.emptySet(); } @Override public Set getRules(RelationSymbol sym) { if (!sym.equals(getQuery().getSymbol())) { return Collections.emptySet(); } return Collections.singleton(queryRule); } @Override public SymbolManager getSymbolManager() { return origProg.getSymbolManager(); } @Override public boolean hasQuery() { return true; } @Override public UserPredicate getQuery() { return queryRule.getHead(); } @Override public FunctionCallFactory getFunctionCallFactory() { return origProg.getFunctionCallFactory(); } @Override public Set getUninterpretedFunctionSymbols() { return origProg.getUninterpretedFunctionSymbols(); } @Override public Set getTypeSymbols() { return origProg.getTypeSymbols(); } }; } private BasicProgram transformNoQuery( boolean useDemandTransformation, boolean restoreStratification) throws InvalidProgramException { topDownIsDefault = false; Set bottomUpSymbols = new HashSet<>(); for (RelationSymbol sym : origProg.getRuleSymbols()) { if (!sym.isTopDown()) { bottomUpSymbols.add(sym); } } Set> adRules = adorn(bottomUpSymbols); Set magicRules = makeMagicRules(adRules); BasicProgram magicProg = new ProgramImpl(magicRules, null); if (restoreStratification && !isStratified(magicProg)) { magicProg = stratify(magicProg, Pair.map(adRules, (rule, num) -> rule)); } if (useDemandTransformation) { magicProg = applyDemandTransformation(magicProg, restoreStratification); } return magicProg; } private BasicProgram applyDemandTransformation(BasicProgram prog, boolean mustBeStratified) throws InvalidProgramException { BasicProgram prog2 = stripAdornments(prog); if (isStratified(prog2)) { return prog2; } return prog; } private BasicProgram stripAdornments(BasicProgram prog) throws InvalidProgramException { Set rules = new HashSet<>(); for (RelationSymbol sym : prog.getRuleSymbols()) { for (BasicRule r : prog.getRules(sym)) { UserPredicate newHead = stripAdornment(r.getHead()); List newBody = new ArrayList<>(); for (ComplexLiteral atom : r) { newBody.add(stripAdornment(atom)); } rules.add(BasicRule.make(newHead, newBody)); } } UserPredicate query = null; if (prog.hasQuery()) { query = stripAdornment(prog.getQuery()); } return new ProgramImpl(rules, query); } private static C stripAdornment(C atom) { return atom.accept( new ComplexLiteralVisitor() { @SuppressWarnings("unchecked") @Override public C visit(UnificationPredicate unificationPredicate, Void input) { return (C) unificationPredicate; } @SuppressWarnings("unchecked") @Override public C visit(UserPredicate userPredicate, Void input) { RelationSymbol sym = userPredicate.getSymbol(); if (sym instanceof PositiveSymbol) { sym = ((PositiveSymbol) sym).getBaseSymbol(); if (sym instanceof AdornedSymbol) { sym = ((AdornedSymbol) sym).getBaseSymbol(); } sym = new PositiveSymbol(sym); } else if (sym instanceof AdornedSymbol) { sym = ((AdornedSymbol) sym).getBaseSymbol(); } return (C) UserPredicate.make(sym, userPredicate.getArgs(), userPredicate.isNegated()); } }, null); } private Set> adorn(Set seeds) throws InvalidProgramException { if (debug) { System.err.println("Adorning rules..."); } Set> adRules = new HashSet<>(); DedupWorkList worklist = new DedupWorkList<>(); for (RelationSymbol seed : seeds) { worklist.push(seed); } HiddenPredicateFinder hpf = new HiddenPredicateFinder(origProg); int ruleNum = 0; while (!worklist.isEmpty()) { RelationSymbol adSym = worklist.pop(); RelationSymbol origSym = adSym; if (adSym instanceof AdornedSymbol) { origSym = ((AdornedSymbol) adSym).getBaseSymbol(); } for (BasicRule r : origProg.getRules(origSym)) { for (RelationSymbol sym : hpf.visit(r)) { if (exploreTopDown(sym)) { throw new InvalidProgramException( "Cannot refer to top-down IDB predicate " + sym + " in a function; consider annotating " + sym + " with @bottomup"); } if (sym.isIdbSymbol()) { worklist.push(sym); } } UserPredicate head = r.getHead(); UserPredicate adHead = UserPredicate.make(adSym, head.getArgs(), head.isNegated()); BasicRule adRule = Adornments.adornRule(adHead, Util.iterableToList(r), topDownIsDefault); for (ComplexLiteral a : adRule) { a.accept( new ComplexLiteralVisitor() { @Override public Void visit(UnificationPredicate unificationPredicate, Void input) { // Do nothing return null; } @Override public Void visit(UserPredicate userPredicate, Void input) { RelationSymbol sym = userPredicate.getSymbol(); if (sym.isIdbSymbol()) { worklist.push(sym); } return null; } }, null); } if (debug) { System.err.println("--" + ruleNum + "--\n" + adRule); } adRules.add(new Pair<>(adRule, ruleNum)); ruleNum++; } } return adRules; } private Set makeMagicRules(Set> adornedRules) { Set magicRules = new HashSet<>(); for (Pair p : adornedRules) { magicRules.addAll(makeMagicRules(p.fst(), p.snd())); } return magicRules; } private boolean exploreTopDown(RelationSymbol sym) { if (sym instanceof AdornedSymbol) { sym = ((AdornedSymbol) sym).getBaseSymbol(); } if (!sym.isIdbSymbol()) { return false; } return sym.isTopDown() || (topDownIsDefault && !sym.isBottomUp()); } private Set makeMagicRules(BasicRule r, int number) { int[] supCount = {0}; Set magicRules = new HashSet<>(); List> liveVarsByAtom = liveVarsByAtom(r); List l = new ArrayList<>(); UserPredicate head = r.getHead(); Set curLiveVars = new HashSet<>(); if (exploreTopDown(head.getSymbol())) { UserPredicate inputAtom = createInputAtom(head); l.add(inputAtom); curLiveVars.addAll(inputAtom.varSet()); } int i = 0; for (ComplexLiteral a : r) { Set futureLiveVars = liveVarsByAtom.get(i); Set nextLiveVars = curLiveVars.stream().filter(futureLiveVars::contains).collect(Collectors.toSet()); l = a.accept( new ComplexLiteralVisitor, List>() { @Override public List visit( UnificationPredicate unificationPredicate, List l) { l.add(a); return l; } @Override public List visit( UserPredicate userPredicate, List l) { RelationSymbol sym = userPredicate.getSymbol(); if (exploreTopDown(sym)) { Set supVars = a.varSet().stream() .filter(curLiveVars::contains) .collect(Collectors.toSet()); supVars.addAll(nextLiveVars); UserPredicate supAtom = createSupAtom(supVars, number, supCount[0], head.getSymbol()); magicRules.add(BasicRule.make(supAtom, l)); magicRules.add( BasicRule.make( createInputAtom(userPredicate), Collections.singletonList(supAtom))); l = new ArrayList<>(); l.add(supAtom); l.add(a); supCount[0]++; } else { l.add(a); } return l; } }, l); curLiveVars.clear(); curLiveVars.addAll(nextLiveVars); for (Var v : a.varSet()) { if (futureLiveVars.contains(v)) { curLiveVars.add(v); } } i++; } magicRules.add(BasicRule.make(head, l)); return magicRules; } private List> liveVarsByAtom(BasicRule r) { List> liveVars = new ArrayList<>(); Set acc = r.getHead().varSet(); liveVars.add(acc); for (int i = r.getBodySize() - 1; i > 0; i--) { acc = new HashSet<>(acc); acc.addAll(r.getBody(i).varSet()); liveVars.add(acc); } Collections.reverse(liveVars); return liveVars; } private UserPredicate createSupAtom( Set curLiveVars, int ruleNum, int supCount, Symbol headSym) { List l = new ArrayList<>(curLiveVars); l.sort( new Comparator() { @Override public int compare(Term o1, Term o2) { return o1.toString().compareTo(o2.toString()); } }); Term[] args = l.toArray(new Term[0]); SupSymbol supSym = new SupSymbol(ruleNum, supCount, args.length); return UserPredicate.make(supSym, args, false); } private UserPredicate createInputAtom(UserPredicate a) { AdornedSymbol headSym = (AdornedSymbol) a.getSymbol(); InputSymbol inputSym = new InputSymbol(headSym); Term[] inputArgs = new Term[inputSym.getArity()]; Term[] args = a.getArgs(); boolean[] adornment = headSym.getAdornment(); for (int i = 0, j = 0; i < args.length; i++) { if (adornment[i]) { inputArgs[j] = args[i]; j++; } } return UserPredicate.make(inputSym, inputArgs, false); } public static class SupSymbol implements RelationSymbol { private final int ruleNum; private final int supCount; private final int arity; public SupSymbol(int ruleNum, int supCount, int arity) { this.ruleNum = ruleNum; this.supCount = supCount; this.arity = arity; } @Override public int getArity() { return arity; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + arity; result = prime * result + ruleNum; result = prime * result + supCount; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SupSymbol other = (SupSymbol) obj; if (arity != other.arity) return false; if (ruleNum != other.ruleNum) return false; if (supCount != other.supCount) return false; return true; } @Override public String toString() { return "sup_" + ruleNum + "_" + supCount; } @Override public FunctorType getCompileTimeType() { throw new UnsupportedOperationException(); } @Override public boolean isIdbSymbol() { return true; } @Override public boolean isDisk() { return false; } @Override public boolean isBottomUp() { throw new UnsupportedOperationException(); } @Override public boolean isTopDown() { throw new UnsupportedOperationException(); } } public static class InputSymbol extends AbstractWrappedRelationSymbol { private final int arity; public InputSymbol(AdornedSymbol baseSymbol) { super(baseSymbol); int nbound = 0; for (boolean b : getBaseSymbol().getAdornment()) { nbound += b ? 1 : 0; } arity = nbound; } @Override public int getArity() { return arity; } @Override public String toString() { return "input_" + getBaseSymbol(); } @Override public FunctorType getCompileTimeType() { throw new UnsupportedOperationException(); } @Override public boolean isBottomUp() { throw new UnsupportedOperationException(); } @Override public boolean isTopDown() { throw new UnsupportedOperationException(); } } private boolean isStratified(BasicProgram p) { try { Stratifier stratifier = new Stratifier(p); for (Stratum s : stratifier.stratify()) { if (s.hasRecursiveNegationOrAggregation()) { return false; } } return true; } catch (InvalidProgramException e) { return false; } } private Set adjustAdornedRules(Collection adRules) { Set newRules = new HashSet<>(); for (BasicRule r : adRules) { UserPredicate head = r.getHead(); if (exploreTopDown(head.getSymbol())) { List body = new ArrayList<>(); body.add(createInputAtom(head)); r.forEach(body::add); newRules.add(BasicRule.make(head, body)); } else { newRules.add(r); } } return newRules; } private ProgramImpl stratify(BasicProgram p, Set adornedRules) throws InvalidProgramException { Set newRules = new HashSet<>(); for (RelationSymbol sym : p.getRuleSymbols()) { for (BasicRule r : p.getRules(sym)) { UserPredicate head = makePositive(r.getHead()); List body = makePositive(r); newRules.add(BasicRule.make(head, body)); } } newRules.addAll(adjustAdornedRules(adornedRules)); return new ProgramImpl(newRules, p.getQuery()); } private C makePositive(C atom) { return atom.accept( new ComplexLiteralVisitor() { @SuppressWarnings("unchecked") @Override public C visit(UnificationPredicate unificationPredicate, Void input) { return (C) unificationPredicate; } @SuppressWarnings("unchecked") @Override public C visit(UserPredicate userPredicate, Void input) { RelationSymbol sym = userPredicate.getSymbol(); if (sym.isIdbSymbol() && !(sym instanceof InputSymbol || sym instanceof SupSymbol)) { if (userPredicate.isNegated()) { return null; } userPredicate = UserPredicate.make(new PositiveSymbol(sym), userPredicate.getArgs(), false); } return (C) userPredicate; } }, null); } private List makePositive(Iterable atoms) { List l = new ArrayList<>(); for (ComplexLiteral a : atoms) { ComplexLiteral b = makePositive(a); if (b != null) { l.add(b); } } return l; } private static class PositiveSymbol extends AbstractWrappedRelationSymbol { public PositiveSymbol(RelationSymbol baseSymbol) { super(baseSymbol); } @Override public String toString() { return "p_" + getBaseSymbol(); } @Override public FunctorType getCompileTimeType() { throw new UnsupportedOperationException(); } @Override public boolean isBottomUp() { throw new UnsupportedOperationException(); } @Override public boolean isTopDown() { throw new UnsupportedOperationException(); } } private static class HiddenPredicateFinder { private final Program origProg; private final Set visitedFunctions = new HashSet<>(); private final Set seenPredicates = new HashSet<>(); public HiddenPredicateFinder(Program origProg) { this.origProg = origProg; } private void findHiddenPredicates(Term t, Set s) { t.accept(predicatesInTermExtractor, s); } public Set allSeenPredicates() { return seenPredicates; } public Set visit(UserFunctionDef def) { Set s = new HashSet<>(); if (visitedFunctions.add(def.getSymbol())) { findHiddenPredicates(def.getBody(), s); } return s; } private void visit(ComplexLiteral l, Set s) { l.accept( new ComplexLiteralVisitor() { @Override public Void visit(UnificationPredicate unificationPredicate, Void input) { findHiddenPredicates(unificationPredicate.getLhs(), s); findHiddenPredicates(unificationPredicate.getRhs(), s); return null; } @Override public Void visit(UserPredicate userPredicate, Void input) { for (Term t : userPredicate.getArgs()) { findHiddenPredicates(t, s); } return null; } }, null); } public Set visit( Rule rule) { Set s = new HashSet<>(); visit(rule.getHead(), s); for (ComplexLiteral l : rule) { visit(l, s); } return s; } private TermVisitor, Void> predicatesInTermExtractor = new TermVisitor, Void>() { @Override public Void visit(Var t, Set in) { return null; } @Override public Void visit(Constructor c, Set in) { for (Term t : c.getArgs()) { t.accept(this, in); } return null; } @Override public Void visit(Primitive p, Set in) { return null; } @Override public Void visit(Expr e, Set in) { e.accept(predicatesInExprExtractor, in); return null; } }; private ExprVisitor, Void> predicatesInExprExtractor = new ExprVisitor, Void>() { @Override public Void visit(MatchExpr matchExpr, Set in) { matchExpr.getMatchee().accept(predicatesInTermExtractor, in); for (MatchClause cl : matchExpr) { cl.getRhs().accept(predicatesInTermExtractor, in); } return null; } @Override public Void visit(FunctionCall funcCall, Set in) { FunctionSymbol sym = funcCall.getSymbol(); if (visitedFunctions.add(sym)) { if (sym instanceof PredicateFunctionSymbol) { RelationSymbol psym = ((PredicateFunctionSymbol) sym).getPredicateSymbol(); seenPredicates.add(psym); in.add(psym); } FunctionDef def = origProg.getDef(sym); if (def instanceof UserFunctionDef) { ((UserFunctionDef) def).getBody().accept(predicatesInTermExtractor, in); } } for (Term t : funcCall.getArgs()) { t.accept(predicatesInTermExtractor, in); } return null; } @Override public Void visit(LetFunExpr funcDef, Set in) { throw new AssertionError("impossible"); } @Override public Void visit(Fold fold, Set in) { fold.getShamCall().accept(this, in); return null; } }; } private class ProgramImpl implements BasicProgram { private final Map> rules = new HashMap<>(); private final Map> facts = new HashMap<>(); private final UserPredicate query; public ProgramImpl(Set rs, UserPredicate query) throws InvalidProgramException { SymbolManager sm = origProg.getSymbolManager(); HiddenPredicateFinder hpf = new HiddenPredicateFinder(origProg); for (BasicRule r : rs) { RelationSymbol headSym = r.getHead().getSymbol(); Util.lookupOrCreate(rules, headSym, HashSet::new).add(r); sm.registerSymbol(headSym); for (ComplexLiteral l : r) { if (l instanceof UserPredicate) { RelationSymbol sym = ((UserPredicate) l).getSymbol(); sm.registerSymbol(sym); if (sym.isEdbSymbol()) { facts.putIfAbsent(sym, origProg.getFacts(sym)); } else { rules.putIfAbsent(sym, new HashSet<>()); } } } hpf.visit(r); } for (FunctionSymbol sym : origProg.getFunctionSymbols()) { FunctionDef def = origProg.getDef(sym); if (def instanceof UserFunctionDef) { hpf.visit((UserFunctionDef) def); } } for (RelationSymbol psym : hpf.allSeenPredicates()) { if (exploreTopDown(psym)) { throw new InvalidProgramException( "Cannot refer to top-down IDB predicate " + psym + " in a function; consider annotating " + psym + " with @bottomup"); } if (psym.isEdbSymbol()) { facts.putIfAbsent(psym, origProg.getFacts(psym)); } if (psym.isIdbSymbol()) { rules.putIfAbsent(psym, new HashSet<>()); } } // Do not keep unnecessary facts around if there is a query. if (query == null) { for (RelationSymbol sym : origProg.getFactSymbols()) { facts.putIfAbsent(sym, origProg.getFacts(sym)); } } this.query = query; } @Override public Set getFunctionSymbols() { return origProg.getFunctionSymbols(); } @Override public Set getFactSymbols() { return Collections.unmodifiableSet(facts.keySet()); } @Override public Set getRuleSymbols() { return Collections.unmodifiableSet(rules.keySet()); } @Override public FunctionDef getDef(FunctionSymbol sym) { return origProg.getDef(sym); } @Override public Set getFacts(RelationSymbol sym) { assert sym.isEdbSymbol(); return Util.lookupOrCreate(facts, sym, () -> Collections.emptySet()); } @Override public Set getRules(RelationSymbol sym) { assert sym.isIdbSymbol(); return Util.lookupOrCreate(rules, sym, () -> Collections.emptySet()); } @Override public SymbolManager getSymbolManager() { return origProg.getSymbolManager(); } @Override public boolean hasQuery() { return query != null; } @Override public UserPredicate getQuery() { return query; } @Override public FunctionCallFactory getFunctionCallFactory() { return origProg.getFunctionCallFactory(); } @Override public Set getUninterpretedFunctionSymbols() { return origProg.getUninterpretedFunctionSymbols(); } @Override public Set getTypeSymbols() { return origProg.getTypeSymbols(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/FactFileParser.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TabSeparatedTermLineContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TsvFileContext; import java.util.Set; class FactFileParser { private final ParsingContext pc; public FactFileParser(ParsingContext parsingContext) { pc = parsingContext; } public void loadFacts(TsvFileContext ctx, int expectedArity, Set acc) throws ParseException { TermExtractor termExtractor = new TermExtractor(pc); VariableCheckPass varChecker = new VariableCheckPass(pc.symbolManager()); for (TabSeparatedTermLineContext l : ctx.tabSeparatedTermLine()) { Term[] args = termExtractor.extractArray(l.term()); if (args.length != expectedArity) { throw new ParseException( l.start.getLine(), "Arity mismatch: expected " + expectedArity + " terms, but got " + args.length); } try { args = varChecker.checkFact(args); } catch (VariableCheckPassException e) { throw new ParseException(l.start.getLine(), e.getMessage()); } acc.add(args); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/Identifier.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; public class Identifier { private final Object val; private Identifier(Object val) { this.val = val; } public static Identifier make(Var var) { return new Identifier(var); } public static Identifier make(FunctionSymbol sym) { return new Identifier(sym); } public boolean isFunctionSymbol() { return val instanceof FunctionSymbol; } public boolean isVar() { return val instanceof Var; } public FunctionSymbol asFunctionSymbol() { return (FunctionSymbol) val; } public Var asVar() { return (Var) val; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/ParseException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; public class ParseException extends Exception { private static final long serialVersionUID = 47248799671953000L; private final String fileName; private final int lineNo; /** Constructs an exception signifying a parsing error. */ public ParseException(String fileName, int lineNo, String message) { super(message); this.fileName = fileName; this.lineNo = lineNo; } /** Constructs an exception signifying a parsing error. */ public ParseException(int lineNo, String message) { this(null, lineNo, message); } public ParseException(UncheckedParseException e) { super(e.getMessage()); this.fileName = e.getFileName(); this.lineNo = e.getLineNo(); } public String getFileName() { return fileName; } public int getLineNo() { return lineNo; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/Parser.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogLexer; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ProgContext; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.antlr.v4.runtime.BufferedTokenStream; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.atn.PredictionMode; public class Parser { private final ParsingContext pc = new ParsingContext(); private FormulogParser getParser(Reader r, boolean isTsv) throws ParseException { try { CharStream chars = CharStreams.fromReader(r); FormulogLexer lexer = new FormulogLexer(chars); TokenStream tokens = isTsv ? new BufferedTokenStream(lexer) : new CommonTokenStream(lexer); return new FormulogParser(tokens); } catch (IOException e) { throw new ParseException(0, e.getMessage()); } } public BasicProgram parse(Reader r) throws ParseException { return parse(r, Collections.emptyList()); } public BasicProgram parse(Reader r, List inputDirs) throws ParseException { try { FormulogParser parser = getParser(r, false); ProgContext progCtx = parser.prog(); Pair> p = new TopLevelParser(pc).parse(progCtx); BasicProgram prog = p.fst(); loadExternalEdbs(prog, p.snd(), inputDirs); return prog; } catch (UncheckedParseException e) { throw new ParseException(e); } } public Set parseFacts(RelationSymbol sym, Reader factStream) throws ParseException { Set facts = new HashSet<>(); FormulogParser parser = getParser(factStream, true); FactFileParser fpp = new FactFileParser(pc); fpp.loadFacts(parser.tsvFile(), sym.getArity(), facts); return facts; } private void loadExternalEdbs( Program prog, Set rels, List inputDirs) throws ParseException { if (rels.isEmpty() || inputDirs.isEmpty()) { return; } ExecutorService exec = Executors.newFixedThreadPool(Main.parallelism); List> tasks = new ArrayList<>(); for (Path inputDir : inputDirs) { for (RelationSymbol sym : rels) { tasks.add( exec.submit( new Runnable() { @Override public void run() { try { readEdbFromFile(sym, inputDir, prog.getFacts(sym)); } catch (ParseException e) { throw new UncheckedParseException(e); } } })); } } exec.shutdown(); try { for (Future task : tasks) { task.get(); } } catch (InterruptedException e) { throw new ParseException(0, e.getMessage()); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof UncheckedParseException) { throw new ParseException((UncheckedParseException) cause); } throw new ParseException(0, e.getMessage()); } } private void readEdbFromFile(RelationSymbol sym, Path inputDir, Set acc) throws ParseException { Path path = inputDir.resolve(sym.toString() + ".tsv"); try (FileReader fr = new FileReader(path.toFile())) { FormulogParser parser = getParser(fr, true); parser.getInterpreter().setPredictionMode(PredictionMode.SLL); FactFileParser fpp = new FactFileParser(pc); fpp.loadFacts(parser.tsvFile(), sym.getArity(), acc); } catch (FileNotFoundException e) { throw new ParseException(0, "Could not find external fact file: " + path); } catch (IOException e) { throw new ParseException(path.toString(), 0, e.getMessage()); } catch (UncheckedParseException e) { throw new ParseException(path.toString(), e.getLineNo(), e.getMessage()); } catch (ParseException e) { throw new ParseException(path.toString(), e.getLineNo(), e.getMessage()); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/ParsingContext.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.types.TypeManager; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; class ParsingContext { private final SymbolManager sm = new SymbolManager(); private final FunctionDefManager fdm = new FunctionDefManager(); private final FunctionCallFactory fcf = new FunctionCallFactory(fdm); private final Map> rl = new HashMap<>(); private final Map cl = new HashMap<>(); private final TypeManager tm = new TypeManager(); private final Map nfc = new HashMap<>(); public SymbolManager symbolManager() { return sm; } public FunctionCallFactory functionCallFactory() { return fcf; } public FunctionDefManager functionDefManager() { return fdm; } public Map> recordLabels() { return rl; } public Map constructorLabels() { return cl; } public TypeManager typeManager() { return tm; } public Map nestedFunctionCounters() { return nfc; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/ParsingUtil.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import static edu.harvard.seas.pl.formulog.util.Util.map; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogBaseVisitor; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FunDefLHSContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.IntParamContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ParameterContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ParameterListContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeParamContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.WildCardParamContext; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; final class ParsingUtil { private ParsingUtil() { throw new AssertionError(); } public static List extractParams(ParsingContext pc, ParameterListContext ctx) { List l = new ArrayList<>(); for (ParameterContext param : ctx.parameter()) { l.add(extractParam(pc, param)); } return l; } public static Param extractParam(ParsingContext pc, ParameterContext ctx) { return ctx.accept( new FormulogBaseVisitor() { @Override public Param visitWildCardParam(WildCardParamContext ctx) { return Param.wildCard(); } @Override public Param visitTypeParam(TypeParamContext ctx) { TypeExtractor typeExtractor = new TypeExtractor(pc); return Param.wildCard(typeExtractor.extract(ctx.type())); } @Override public Param visitIntParam(IntParamContext ctx) { return Param.nat(Integer.parseInt(ctx.INT().getText())); } }); } public static Pair> extractFunDeclaration( ParsingContext pc, FunDefLHSContext ctx, boolean isNested) { String name = ctx.ID().getText(); if (pc.symbolManager().hasConstructorSymbolWithName(name)) { throw new RuntimeException("Cannot create a function with name of constructor: " + name); } if (isNested) { AtomicInteger cnt = Util.lookupOrCreate(pc.nestedFunctionCounters(), name, AtomicInteger::new); name += "$" + cnt.getAndIncrement(); } TypeExtractor typeExtractor = new TypeExtractor(pc); List argTypes = typeExtractor.extract(ctx.args.type()); Type retType = typeExtractor.extract(ctx.retType); FunctionSymbol sym = pc.symbolManager() .createFunctionSymbol(name, argTypes.size(), new FunctorType(argTypes, retType)); List args = map(ctx.args.var(), x -> Var.fresh(x.getText())); if (args.size() != new HashSet<>(args).size()) { throw new RuntimeException( "Cannot use the same variable multiple times in a function declaration: " + name); } return new Pair<>(sym, args); } public static List>> extractFunDeclarations( ParsingContext pc, List ctxs, boolean isNested) { return map(ctxs, ctx -> extractFunDeclaration(pc, ctx, isNested)); } public static Map varsToIds(Iterable vars) { Map m = new HashMap<>(); for (Var x : vars) { m.put(x.getName(), Identifier.make(x)); } return m; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/TermExtractor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2024 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.ast.BoolTerm; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.FP32; import edu.harvard.seas.pl.formulog.ast.FP64; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.I32; import edu.harvard.seas.pl.formulog.ast.I64; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.NestedFunctionDef; import edu.harvard.seas.pl.formulog.ast.StringTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogBaseVisitor; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.BinopFormulaContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.BinopTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ConsTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.DoubleTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FloatTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FoldTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FormulaTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FunDefLHSContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.HoleTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.I32TermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.I64TermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.IfExprContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.IndexedFunctorContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.IteTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.LetExprContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.LetFormulaContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.LetFunExprContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ListTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.MatchClauseContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.MatchExprContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.NonEmptyTermListContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.NotFormulaContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.OutermostCtorContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ParensTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.QuantifiedFormulaContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RecordEntryContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RecordTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RecordUpdateTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.SpecialFPTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.StringTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TermSymFormulaContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TupleTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.UnopTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.VarTermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogVisitor; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParamKind; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedSymbol; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.StackMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; class TermExtractor { private final ParsingContext pc; private final StackMap env = new StackMap<>(); public TermExtractor(ParsingContext parsingContext) { pc = parsingContext; env.push(new HashMap<>()); } public synchronized Term extract(TermContext ctx) { try { Term t = visitor.visit(ctx); return t; } catch (UncheckedParseException e) { throw e; } catch (Exception e) { throw new UncheckedParseException(ctx.start.getLine(), e.getMessage()); } } public synchronized List extractList(List ctxs) { List terms = new ArrayList<>(); for (TermContext ctx : ctxs) { terms.add(extract(ctx)); } return terms; } public synchronized Term[] extractArray(List ctxs) { Term[] terms = new Term[ctxs.size()]; int i = 0; for (TermContext ctx : ctxs) { terms[i] = visitor.visit(ctx); i++; } return terms; } public synchronized void pushIds(Map ids) { env.push(ids); } public synchronized Map popIds() { return env.pop(); } private final FormulogVisitor visitor = new FormulogBaseVisitor() { private boolean inFormula; private boolean inPattern; private void assertNotInFormula(int lineNo, String msg) { if (inFormula) { throw new UncheckedParseException(lineNo, msg); } } private void toggleInFormula() { inFormula = !inFormula; } @Override public Term visitHoleTerm(HoleTermContext ctx) { return Var.makeHole(); } @Override public Term visitVarTerm(VarTermContext ctx) { String name = ctx.VAR().getText(); Identifier id = env.get(name); if (id == null) { id = Identifier.make(Var.fresh(name)); env.put(name, id); } assert id.isVar(); return id.asVar(); } @Override public Term visitStringTerm(StringTermContext ctx) { String s = ctx.QSTRING().getText(); return StringTerm.make(s.substring(1, s.length() - 1)); } @Override public Term visitConsTerm(ConsTermContext ctx) { Term[] args = extractArray(ctx.term()); return Constructors.make(BuiltInConstructorSymbol.CONS, args); } private boolean isSpecialFormulaCtor(String name) { if (pc.symbolManager().hasName(name)) { Symbol sym = pc.symbolManager().lookupSymbol(name); return sym instanceof ConstructorSymbol && isSpecialFormulaCtor((ConstructorSymbol) sym); } return false; } // For a couple constructors, we want to make sure that their arguments are // forced to be non-formula types. For example, the constructor bv_const needs // to take something of type i32, not i32 smt. private boolean isSpecialFormulaCtor(ConstructorSymbol sym) { if (sym instanceof ParameterizedConstructorSymbol) { switch (((ParameterizedConstructorSymbol) sym).getBase()) { case BV_BIG_CONST: case BV_CONST: case BV_EXTRACT: case FP_BIG_CONST: case FP_CONST: return true; default: break; } } else if (sym instanceof BuiltInConstructorSymbol) { switch ((BuiltInConstructorSymbol) sym) { case INT_BIG_CONST: case INT_CONST: return true; default: break; } } return false; } @Override public Term visitIndexedFunctor(IndexedFunctorContext ctx) { String name = ctx.id.getText(); if (name.equals("true") || name.equals("false")) { return parseBool(ctx); } boolean wasInFormula = inFormula; boolean isSpecial = isSpecialFormulaCtor(name); if (isSpecial) { inFormula = false; } List params = ParsingUtil.extractParams(pc, ctx.parameterList()); Term[] args = extractArray(ctx.termArgs().term()); if (inPattern && args.length == 0 && !pc.symbolManager().hasConstructorSymbolWithName(name)) { Var x = Var.fresh(name); env.put(name, Identifier.make(x)); } Identifier id = env.get(name); if (id != null && id.isVar()) { if (args.length > 0) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot apply a variable " + name + " to arguments."); } return id.asVar(); } if (name.charAt(0) == '#' && args.length == 0) { if (params.size() != 1) { throw new IllegalArgumentException( "Expected a single parameter to solver variable: " + name); } Type ty = params.get(0).getType(); return extractSolverSymbol(StringTerm.make(name.substring(1)), ty); } Symbol sym = id != null ? id.asFunctionSymbol() : pc.symbolManager().lookupSymbol(name); if (sym instanceof ParameterizedSymbol) { sym = ((ParameterizedSymbol) sym).copyWithNewArgs(params); } else if (!params.isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Symbol " + sym + " is not parametric."); } adjustFunctorArgs(sym, args); Term t = makeFunctor(ctx.start.getLine(), sym, args); if (isSpecial) { t = makeExitFormula(t); } inFormula = wasInFormula; return t; } private void adjustFunctorArgs(Symbol sym, Term[] args) { if (sym instanceof ParameterizedConstructorSymbol) { switch (((ParameterizedConstructorSymbol) sym).getBase()) { case BV_EXTRACT: args[0] = makeEnterFormula(args[0]); break; default: break; } } } private Term parseBool(IndexedFunctorContext ctx) { String name = ctx.id.getText(); assert name.equals("true") || name.equals("false"); boolean val = name.equals("true"); if (!ctx.parameterList().parameter().isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Boolean value " + val + " cannot be parameterized"); } if (!ctx.termArgs().term().isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Boolean value " + val + " cannot be applied to arguments"); } return BoolTerm.mk(val); } private Term makeFunctor(int lineNo, Symbol sym, Term[] args) { if (sym.getArity() != args.length) { throw new UncheckedParseException( lineNo, "Symbol " + sym + " has arity " + sym.getArity() + ", but applied to " + args.length + " arg(s): " + Arrays.toString(args)); } if (sym instanceof RelationSymbol) { FunctionSymbol newSym = pc.symbolManager().createPredicateFunctionSymbolPlaceholder((RelationSymbol) sym); return pc.functionCallFactory().make(newSym, args); } else if (sym instanceof FunctionSymbol) { Term t = pc.functionCallFactory().make((FunctionSymbol) sym, args); if (sym.getArity() > 0) { assertNotInFormula( lineNo, "Cannot invoke a non-nullary function from within a formula: " + t); } return t; } else if (sym instanceof ConstructorSymbol) { ConstructorSymbol csym = (ConstructorSymbol) sym; Term t = Constructors.make(csym, args); return t; } else { throw new UncheckedParseException( lineNo, "Cannot create a term with non-constructor, non-function symbol " + sym + "."); } } @Override public Term visitFoldTerm(FoldTermContext ctx) { assertNotInFormula( ctx.start.getLine(), "Cannot invoke a fold from within a formula: " + ctx.getText()); String name = ctx.ID().getText(); Identifier id = env.get(name); if (id != null && id.isVar()) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot use a variable as the function to fold: " + name); } Symbol sym = id != null ? id.asFunctionSymbol() : pc.symbolManager().lookupSymbol(name); if (!(sym instanceof FunctionSymbol)) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot fold over non-function: " + sym); } if (sym.getArity() != 2) { throw new UncheckedParseException( ctx.start.getLine(), "Can only fold over a binary function, but " + sym + " has arity " + sym.getArity()); } Term[] args = extractArray(ctx.termArgs().term()); return Fold.mk((FunctionSymbol) sym, args, pc.functionCallFactory()); } @Override public Term visitTupleTerm(TupleTermContext ctx) { Term[] args = extractArray(ctx.tuple().term()); return Constructors.make(GlobalSymbolManager.lookupTupleSymbol(args.length), args); } private final Pattern hex = Pattern.compile("0x([0-9a-fA-F]+)[lL]?"); @Override public Term visitI32Term(I32TermContext ctx) { Matcher m = hex.matcher(ctx.val.getText()); int n; if (m.matches()) { n = Integer.parseUnsignedInt(m.group(1).toUpperCase(), 16); } else { n = Integer.parseInt(ctx.val.getText()); } return I32.make(n); } @Override public Term visitI64Term(I64TermContext ctx) { Matcher m = hex.matcher(ctx.val.getText()); long n; if (m.matches()) { n = Long.parseUnsignedLong(m.group(1).toUpperCase(), 16); } else { // Long.parseLong does not allow trailing l or L. String text = ctx.val.getText(); String sub = text.substring(0, text.length() - 1); n = Long.parseLong(sub); } return I64.make(n); } @Override public Term visitFloatTerm(FloatTermContext ctx) { return FP32.make(Float.parseFloat(ctx.val.getText())); } @Override public Term visitDoubleTerm(DoubleTermContext ctx) { return FP64.make(Double.parseDouble(ctx.getText())); } @Override public Term visitSpecialFPTerm(SpecialFPTermContext ctx) { switch (ctx.val.getType()) { case FormulogParser.FP32_NAN: return FP32.make(Float.NaN); case FormulogParser.FP32_NEG_INFINITY: return FP32.make(Float.NEGATIVE_INFINITY); case FormulogParser.FP32_POS_INFINITY: return FP32.make(Float.POSITIVE_INFINITY); case FormulogParser.FP64_NAN: return FP64.make(Double.NaN); case FormulogParser.FP64_NEG_INFINITY: return FP64.make(Double.NEGATIVE_INFINITY); case FormulogParser.FP64_POS_INFINITY: return FP64.make(Double.POSITIVE_INFINITY); } throw new AssertionError(); } @Override public Term visitRecordTerm(RecordTermContext ctx) { Pair> p = handleRecordEntries(ctx.recordEntries().recordEntry()); ConstructorSymbol csym = p.fst(); Map argMap = p.snd(); Term[] args = new Term[csym.getArity()]; if (args.length != argMap.keySet().size()) { throw new UncheckedParseException( ctx.start.getLine(), "Missing label(s) when creating record of type " + csym); } for (int i = 0; i < args.length; i++) { args[i] = argMap.get(i); } return Constructors.make(csym, args); } @Override public Term visitRecordUpdateTerm(RecordUpdateTermContext ctx) { Pair> p = handleRecordEntries(ctx.recordEntries().recordEntry()); ConstructorSymbol csym = p.fst(); Map argMap = p.snd(); Term[] args = new Term[csym.getArity()]; FunctionSymbol[] labels = pc.constructorLabels().get(csym); Term orig = extract(ctx.term()); for (int i = 0; i < args.length; ++i) { Term t = argMap.get(i); if (t == null) { FunctionSymbol label = labels[i]; t = pc.functionCallFactory().make(label, Terms.singletonArray(orig)); } args[i] = t; } return Constructors.make(csym, args); } private Pair> handleRecordEntries( List entries) { AlgebraicDataType type = null; Map argMap = new HashMap<>(); for (RecordEntryContext entry : entries) { Symbol label = pc.symbolManager().lookupSymbol(entry.ID().getText()); Pair p = pc.recordLabels().get(label); if (p == null) { throw new UncheckedParseException( entry.start.getLine(), label + " is not a record label"); } AlgebraicDataType type2 = p.fst(); if (type == null) { type = type2; } else if (!type.equals(type2)) { throw new UncheckedParseException( entry.start.getLine(), "Cannot use label " + label + " in a record of type " + type); } if (argMap.putIfAbsent(p.snd(), extract(entry.term())) != null) { throw new UncheckedParseException( entry.start.getLine(), "Cannot use the same label " + label + " multiple times when creating a record"); } } ConstructorSymbol csym = type.getConstructors().iterator().next().getSymbol(); return new Pair<>(csym, argMap); } @Override public Term visitUnopTerm(UnopTermContext ctx) { Term t = ctx.term().accept(this); Term r = null; var start = ctx.term().getStart().getType(); switch (ctx.op.getType()) { case FormulogParser.BANG: r = pc.functionCallFactory().make(BuiltInFunctionSymbol.BNOT, new Term[] {t}); break; case FormulogParser.PLUS: if ((t instanceof I32 && start != FormulogParser.HEX) || (t instanceof I64 && start != FormulogParser.HEXL) || (t instanceof FP32) || (t instanceof FP64)) { return t; } break; case FormulogParser.MINUS: if (t instanceof I32 && start != FormulogParser.HEX) { return I32.make(-((I32) t).getVal()); } else if (t instanceof I64 && start != FormulogParser.HEXL) { return I64.make(-((I64) t).getVal()); } else if (t instanceof FP32) { return FP32.make(-((FP32) t).getVal()); } else if (t instanceof FP64) { return FP64.make(-((FP64) t).getVal()); } r = pc.functionCallFactory().make(BuiltInFunctionSymbol.I32_NEG, new Term[] {t}); break; } if (r == null) { throw new UncheckedParseException( ctx.start.getLine(), "Unrecognized unop: " + ctx.getText()); } assertNotInFormula( ctx.start.getLine(), "Cannot invoke a unop from within a formula: " + ctx.getText()); return r; } private Term makeBoolMatch(Term matchee, Term ifTrue, Term ifFalse) { MatchClause matchTrue = MatchClause.make(BoolTerm.mkTrue(), ifTrue); MatchClause matchFalse = MatchClause.make(BoolTerm.mkFalse(), ifFalse); return MatchExpr.make(matchee, Arrays.asList(matchTrue, matchFalse)); } private FunctionSymbol tokenToBinopSym(int tokenType) { switch (tokenType) { case FormulogParser.MUL: return BuiltInFunctionSymbol.I32_MUL; case FormulogParser.DIV: return BuiltInFunctionSymbol.I32_SDIV; case FormulogParser.REM: return BuiltInFunctionSymbol.I32_SREM; case FormulogParser.PLUS: return BuiltInFunctionSymbol.I32_ADD; case FormulogParser.MINUS: return BuiltInFunctionSymbol.I32_SUB; case FormulogParser.AMP: return BuiltInFunctionSymbol.I32_AND; case FormulogParser.CARET: return BuiltInFunctionSymbol.I32_XOR; case FormulogParser.EQ: return BuiltInFunctionSymbol.BEQ; case FormulogParser.NEQ: return BuiltInFunctionSymbol.BNEQ; case FormulogParser.LT: return BuiltInFunctionSymbol.I32_LT; case FormulogParser.LTE: return BuiltInFunctionSymbol.I32_LE; case FormulogParser.GT: return BuiltInFunctionSymbol.I32_GT; case FormulogParser.GTE: return BuiltInFunctionSymbol.I32_GE; default: return null; } } private Term makeNonFunctionBinop(int tokenType, Term lhs, Term rhs) { switch (tokenType) { case FormulogParser.AMPAMP: return makeBoolMatch(lhs, rhs, BoolTerm.mkFalse()); case FormulogParser.BARBAR: return makeBoolMatch(lhs, BoolTerm.mkTrue(), rhs); default: return null; } } @Override public Term visitBinopTerm(BinopTermContext ctx) { Term[] args = {extract(ctx.term(0)), extract(ctx.term(1))}; FunctionSymbol sym = tokenToBinopSym(ctx.op.getType()); Term t; if (sym == null) { t = makeNonFunctionBinop(ctx.op.getType(), args[0], args[1]); } else { t = pc.functionCallFactory().make(sym, args); } if (t == null) { throw new AssertionError("Unrecognized binop: " + ctx.getText()); } assertNotInFormula( ctx.start.getLine(), "Cannot invoke a binop from within a formula: " + ctx.getText()); return t; } @Override public Term visitListTerm(ListTermContext ctx) { Term t = Constructors.makeZeroAry(BuiltInConstructorSymbol.NIL); List ctxs = new ArrayList<>(ctx.list().term()); Collections.reverse(ctxs); for (TermContext tc : ctxs) { t = Constructors.make(BuiltInConstructorSymbol.CONS, new Term[] {extract(tc), t}); } return t; } @Override public Term visitParensTerm(ParensTermContext ctx) { return extract(ctx.term()); } private Term makeExitFormula(Term t) { return Constructors.make(BuiltInConstructorSymbol.EXIT_FORMULA, Terms.singletonArray(t)); } private Term makeEnterFormula(Term t) { return Constructors.make(BuiltInConstructorSymbol.ENTER_FORMULA, Terms.singletonArray(t)); } @Override public Term visitFormulaTerm(FormulaTermContext ctx) { assertNotInFormula( ctx.start.getLine(), "Cannot nest a formula within a formula: " + ctx.getText()); toggleInFormula(); Term t = extract(ctx.term()); t = makeEnterFormula(t); toggleInFormula(); return t; } @Override public Term visitNotFormula(NotFormulaContext ctx) { Term t = extract(ctx.term()); return Constructors.make(BuiltInConstructorSymbol.SMT_NOT, Terms.singletonArray(t)); } @Override public Term visitBinopFormula(BinopFormulaContext ctx) { Term[] args = extractArray(ctx.term()); ConstructorSymbol sym; switch (ctx.op.getType()) { case FormulogParser.FORMULA_EQ: case FormulogParser.IFF: sym = (ConstructorSymbol) pc.symbolManager() .getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_EQ); break; case FormulogParser.IMP: sym = BuiltInConstructorSymbol.SMT_IMP; break; case FormulogParser.AND: sym = BuiltInConstructorSymbol.SMT_AND; break; case FormulogParser.OR: sym = BuiltInConstructorSymbol.SMT_OR; break; default: throw new AssertionError(); } return Constructors.make(sym, args); } @Override public Term visitLetFormula(LetFormulaContext ctx) { ConstructorSymbol sym = (ConstructorSymbol) pc.symbolManager().getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_LET); boolean wasInFormula = inFormula; inFormula = false; Term[] args = extractArray(ctx.term()); inFormula = wasInFormula; args[1] = makeEnterFormula(args[1]); args[2] = makeEnterFormula(args[2]); return makeExitFormula(Constructors.make(sym, args)); } @Override public Term visitQuantifiedFormula(QuantifiedFormulaContext ctx) { Term[] args = new Term[3]; args[0] = parseFormulaVarList(ctx.variables); args[1] = makeEnterFormula(extract(ctx.boundTerm)); args[2] = Constructors.nil(); if (ctx.pattern != null) { args[2] = Constructors.make( BuiltInConstructorSymbol.CONS, new Term[] {(parsePatternList(ctx.pattern)), args[2]}); } ConstructorSymbol sym; switch (ctx.quantifier.getType()) { case FormulogParser.FORALL: sym = BuiltInConstructorSymbol.SMT_FORALL; break; case FormulogParser.EXISTS: sym = BuiltInConstructorSymbol.SMT_EXISTS; break; default: throw new AssertionError("impossible"); } return makeExitFormula(Constructors.make(sym, args)); } private Term parsePatternList(NonEmptyTermListContext ctx) { return parseNonEmptyTermList( ctx, pat -> { ConstructorSymbol sym = (ConstructorSymbol) pc.symbolManager() .getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_PAT); return Constructors.make(sym, Terms.singletonArray(makeEnterFormula(pat))); }); } private Term parseFormulaVarList(NonEmptyTermListContext ctx) { return parseNonEmptyTermList( ctx, var -> { ConstructorSymbol sym = (ConstructorSymbol) pc.symbolManager() .getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_WRAP_VAR); return Constructors.make(sym, Terms.singletonArray(var)); }); } private Term parseNonEmptyTermList( NonEmptyTermListContext ctx, Function transformer) { Term acc = Constructors.nil(); List ctxs = new ArrayList<>(ctx.term()); Collections.reverse(ctxs); for (TermContext tc : ctxs) { Term t = transformer.apply(extract(tc)); acc = Constructors.make(BuiltInConstructorSymbol.CONS, new Term[] {t, acc}); } return acc; } @Override public Term visitIteTerm(IteTermContext ctx) { Term[] args = extractArray(ctx.term()); if (inFormula) { return Constructors.make(BuiltInConstructorSymbol.SMT_ITE, args); } else { return makeBoolMatch(args[0], args[1], args[2]); } } @Override public Term visitTermSymFormula(TermSymFormulaContext ctx) { Type type = ParsingUtil.extractParam(pc, ctx.parameter()).getType(); Term id = extract(ctx.term()); return extractSolverSymbol(id, type); } private Term extractSolverSymbol(Term id, Type type) { ParameterizedConstructorSymbol sym = GlobalSymbolManager.getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_VAR); sym = sym.copyWithNewArgs(Param.wildCard(), new Param(type, ParamKind.PRE_SMT_TYPE)); return makeExitFormula(Constructors.make(sym, Terms.singletonArray(id))); } public Term visitOutermostCtor(OutermostCtorContext ctx) { Symbol sym = pc.symbolManager().lookupSymbol(ctx.ID().getText()); if (!(sym instanceof ConstructorSymbol)) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot use non-constructor symbol " + sym + " in a `not` term."); } Term[] vars = new Term[sym.getArity()]; for (int i = 0; i < vars.length; ++i) { vars[i] = Var.fresh(); } Term pat = Constructors.make((ConstructorSymbol) sym, vars); List clauses = new ArrayList<>(); clauses.add(MatchClause.make(pat, BoolTerm.mkFalse())); clauses.add(MatchClause.make(Var.fresh(), BoolTerm.mkTrue())); Term arg = extract(ctx.term()); return MatchExpr.make(arg, clauses); } @Override public Term visitMatchExpr(MatchExprContext ctx) { Term guard = ctx.term().accept(this); List matches = new ArrayList<>(); for (MatchClauseContext mcc : ctx.matchClause()) { for (TermContext pc : mcc.patterns().term()) { env.push(new HashMap<>()); inPattern = true; Term pattern = extract(pc); inPattern = false; Term rhs = extract(mcc.rhs); env.pop(); MatchClause clause = MatchClause.make(pattern, rhs); matches.add(clause); } } return MatchExpr.make(guard, matches); } @Override public Term visitLetExpr(LetExprContext ctx) { Term assign = ctx.assign.accept(this); env.push(new HashMap<>()); inPattern = true; List ts = new ArrayList<>(); for (TermContext termCtx : ctx.lhs.term()) { ts.add(extract(termCtx)); } Term t; if (ts.size() > 1) { t = Constructors.make( GlobalSymbolManager.lookupTupleSymbol(ts.size()), ts.toArray(Terms.emptyArray())); } else { t = ts.get(0); } inPattern = false; Term body = ctx.body.accept(this); env.pop(); MatchClause m = MatchClause.make(t, body); return MatchExpr.make(assign, Collections.singletonList(m)); } @Override public Term visitLetFunExpr(LetFunExprContext ctx) { if (inFormula) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot define a function from within a formula:\n" + ctx.getText()); } List names = new ArrayList<>(); for (FunDefLHSContext f : ctx.funDefs().funDefLHS()) { String name = f.ID().getText(); if (names.contains(name)) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot use the same name more than once in a mutually-recursive function" + " definition " + name); } names.add(name); } List>> signatures = ParsingUtil.extractFunDeclarations(pc, ctx.funDefs().funDefLHS(), true); Iterator>> sigIt = signatures.iterator(); HashMap m = new HashMap<>(); for (String name : names) { m.put(name, Identifier.make(sigIt.next().fst())); } env.push(m); sigIt = signatures.iterator(); Set defs = new HashSet<>(); for (TermContext bodyCtx : ctx.funDefs().term()) { Pair> p = sigIt.next(); List params = p.snd(); env.push(ParsingUtil.varsToIds(params)); defs.add(NestedFunctionDef.make(p.fst(), params, extract(bodyCtx))); env.pop(); } Term letBody = extract(ctx.letFunBody); env.pop(); return LetFunExpr.make(defs, letBody); } @Override public Term visitIfExpr(IfExprContext ctx) { Term guard = ctx.guard.accept(this); Term thenExpr = ctx.thenExpr.accept(this); Term elseExpr = ctx.elseExpr.accept(this); List branches = new ArrayList<>(); branches.add(MatchClause.make(BoolTerm.mkTrue(), thenExpr)); branches.add(MatchClause.make(BoolTerm.mkFalse(), elseExpr)); return MatchExpr.make(guard, branches); } }; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/TopLevelParser.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import static edu.harvard.seas.pl.formulog.util.Util.map; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.RecordAccessor; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogBaseVisitor; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.AdtDefContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.AnnotationContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ClauseStmtContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ConstructorTypeContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FactStmtContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.FunDeclContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ProgContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.QueryStmtContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RecordDefContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RecordEntryDefContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.RelDeclContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TermContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeAliasContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeDeclContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeDefLHSContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeDefRHSContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.UninterpFunDeclContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.UninterpSortDeclContext; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.MutableRelationSymbol; import edu.harvard.seas.pl.formulog.symbols.PredicateFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RecordSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.symbols.TypeSymbolType; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.TypeAlias; import edu.harvard.seas.pl.formulog.types.Types; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; class TopLevelParser { private final ParsingContext pc; public TopLevelParser(ParsingContext parsingContext) { pc = parsingContext; } public Pair> parse(ProgContext ctx) throws ParseException { TopLevelVisitor visitor = new TopLevelVisitor(); ctx.accept(visitor); BasicProgram prog = visitor.program(); return new Pair<>(prog, visitor.externalEdbs); } private final class TopLevelVisitor extends FormulogBaseVisitor { private final VariableCheckPass varChecker = new VariableCheckPass(pc.symbolManager()); private final TermExtractor termExtractor = new TermExtractor(pc); private final TypeExtractor typeExtractor = new TypeExtractor(pc); private final Map> initialFacts = new HashMap<>(); private final Map> rules = new HashMap<>(); private final Set externalEdbs = new HashSet<>(); private final Set uninterpFuncSymbols = new HashSet<>(); private UserPredicate query; @Override public Void visitFunDecl(FunDeclContext ctx) { var ps = ParsingUtil.extractFunDeclarations(pc, ctx.topLevelFunDefs().funDefLHS(), false); if (ctx.topLevelFunDefs().intro.getType() == FormulogParser.CONST && !ps.get(0).snd().isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot declare a function with 'const' if it takes arguments (use 'fun' instead): " + ps.get(0).fst()); } Iterator bodies = ctx.topLevelFunDefs().term().iterator(); for (Pair> p : ps) { FunctionSymbol sym = p.fst(); List args = p.snd(); termExtractor.pushIds(ParsingUtil.varsToIds(args)); Term body = termExtractor.extract(bodies.next()); termExtractor.popIds(); try { Term newBody = varChecker.checkFunction(args, body); pc.functionDefManager().register(UserFunctionDef.get(sym, args, newBody)); } catch (VariableCheckPassException e) { throw new UncheckedParseException( ctx.start.getLine(), "Error in definition for function " + sym + ": " + e.getMessage() + "\n" + body); } } return null; } @Override public Void visitRelDecl(RelDeclContext ctx) { String name = ctx.ID().getText(); List types = typeExtractor.extract(ctx.maybeAnnotatedTypeList().type()); if (!Types.getTypeVars(types).isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot use type variables in the signature of a relation: " + name); } MutableRelationSymbol sym = pc.symbolManager() .createRelationSymbol(name, types.size(), new FunctorType(types, BuiltInTypes.bool)); for (AnnotationContext actx : ctx.annotation()) { switch (actx.getText()) { case "@bottomup": sym.setBottomUp(); break; case "@topdown": sym.setTopDown(); break; case "@disk": sym.setDisk(); break; case "@edb": sym.setEdb(); break; default: throw new UncheckedParseException( ctx.start.getLine(), "Unrecognized annotation for predicate " + sym + ": " + actx.getText()); } } if (sym.isEdbSymbol()) { initialFacts.put(sym, Util.concurrentSet()); if (sym.isDisk()) { externalEdbs.add(sym); } } else { rules.put(sym, new HashSet<>()); } return null; } @Override public Void visitTypeAlias(TypeAliasContext ctx) { Pair> p = parseTypeDefLHS(ctx.typeDefLHS(), TypeSymbolType.TYPE_ALIAS); TypeSymbol sym = p.fst(); List typeVars = p.snd(); Type type = typeExtractor.extract(ctx.type()); if (!typeVars.containsAll(Types.getTypeVars(type))) { throw new UncheckedParseException( ctx.start.getLine(), "Unbound type variable in definition of " + sym); } pc.typeManager().registerAlias(new TypeAlias(sym, typeVars, type)); return null; } @Override public Void visitTypeDecl(TypeDeclContext ctx) { List>> lhss = map(ctx.typeDefLHS(), lhs -> parseTypeDefLHS(lhs, TypeSymbolType.NORMAL_TYPE)); Iterator it = ctx.typeDefRHS().iterator(); for (Pair> p : lhss) { TypeSymbol sym = p.fst(); List typeVars = p.snd(); AlgebraicDataType type = AlgebraicDataType.make(sym, new ArrayList<>(typeVars)); TypeDefRHSContext rhs = it.next(); if (rhs.adtDef() != null) { handleAdtDef(rhs.adtDef(), type, typeVars); } else { handleRecordDef(rhs.recordDef(), type, typeVars); } } return null; } private void handleAdtDef(AdtDefContext ctx, AlgebraicDataType type, List typeVars) { Set constructors = new HashSet<>(); for (ConstructorTypeContext ctc : ctx.constructorType()) { List typeArgs = typeExtractor.extract(ctc.typeList().type()); ConstructorSymbol csym = pc.symbolManager() .createConstructorSymbol( ctc.ID().getText(), typeArgs.size(), ConstructorSymbolType.VANILLA_CONSTRUCTOR, new FunctorType(typeArgs, type)); if (!typeVars.containsAll(Types.getTypeVars(typeArgs))) { throw new UncheckedParseException( ctx.start.getLine(), "Unbound type variable in definition of " + csym); } pc.symbolManager() .createConstructorSymbol( "#is_" + csym, 1, ConstructorSymbolType.SOLVER_CONSTRUCTOR_TESTER, new FunctorType(type, BuiltInTypes.bool)); List getterSyms = new ArrayList<>(); for (int i = 0; i < csym.getArity(); ++i) { FunctorType t = new FunctorType(type, typeArgs.get(i)); String name = "#" + csym + "_" + (i + 1); getterSyms.add( pc.symbolManager() .createConstructorSymbol( name, 1, ConstructorSymbolType.SOLVER_CONSTRUCTOR_GETTER, t)); } constructors.add(new ConstructorScheme(csym, typeArgs, getterSyms)); } AlgebraicDataType.setConstructors(type.getSymbol(), typeVars, constructors); } private void handleRecordDef( RecordDefContext ctx, AlgebraicDataType type, List typeVars) { List entryTypes = new ArrayList<>(); List getterSyms = new ArrayList<>(); List labels = new ArrayList<>(); int i = 0; for (RecordEntryDefContext entry : ctx.recordEntryDef()) { Type entryType = typeExtractor.extract(entry.type()); entryTypes.add(entryType); FunctorType labelType = new FunctorType(type, entryType); FunctionSymbol label = pc.symbolManager().createFunctionSymbol(entry.ID().getText(), 1, labelType); labels.add(label); final int j = i; pc.functionDefManager().register(new RecordAccessor(label, j)); ConstructorSymbol getter = pc.symbolManager() .createConstructorSymbol( "#" + label, 1, ConstructorSymbolType.SOLVER_CONSTRUCTOR_GETTER, labelType); getterSyms.add(getter); pc.recordLabels().put(label, new Pair<>(type, i)); i++; } TypeSymbol sym = type.getSymbol(); if (!typeVars.containsAll(Types.getTypeVars(entryTypes))) { throw new UncheckedParseException( ctx.start.getLine(), "Unbound type variable in definition of " + sym); } FunctorType ctype = new FunctorType(entryTypes, type); RecordSymbol csym = pc.symbolManager().createRecordSymbol("_rec_" + sym, entryTypes.size(), ctype, labels); ConstructorScheme ctor = new ConstructorScheme(csym, entryTypes, getterSyms); AlgebraicDataType.setConstructors(sym, typeVars, Collections.singleton(ctor)); pc.constructorLabels().put(csym, labels.toArray(new FunctionSymbol[0])); } private Pair> parseTypeDefLHS( TypeDefLHSContext ctx, TypeSymbolType symType) { List typeVars = map(ctx.TYPEVAR(), t -> TypeVar.get(t.getText())); TypeSymbol sym = pc.symbolManager().createTypeSymbol(ctx.ID().getText(), typeVars.size(), symType); if (typeVars.size() != (new HashSet<>(typeVars)).size()) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot use the same type variable multiple times in a type declaration: " + sym); } return new Pair<>(sym, typeVars); } @Override public Void visitUninterpSortDecl(UninterpSortDeclContext ctx) { parseTypeDefLHS(ctx.typeDefLHS(), TypeSymbolType.UNINTERPRETED_SORT); return null; } @Override public Void visitUninterpFunDecl(UninterpFunDeclContext ctx) { ConstructorTypeContext ctc = ctx.constructorType(); List typeArgs = typeExtractor.extract(ctc.typeList().type()); Type type = typeExtractor.extract(ctx.type()); ConstructorSymbol csym = pc.symbolManager() .createConstructorSymbol( ctc.ID().getText(), typeArgs.size(), ConstructorSymbolType.SOLVER_UNINTERPRETED_FUNCTION, new FunctorType(typeArgs, type)); Set allTypes = new HashSet<>(typeArgs); allTypes.add(type); for (Type ty : allTypes) { if (!hasSmtType(ty)) { throw new UncheckedParseException( ctx.start.getLine(), "Uninterpreted function must have an " + BuiltInTypeSymbol.SMT_TYPE + " type: " + csym); } if (!Types.getTypeVars(ty).isEmpty()) { throw new UncheckedParseException( ctx.start.getLine(), "Uninterpreted functions cannot have type variables: " + csym); } } uninterpFuncSymbols.add(csym); return null; } private boolean hasSmtType(Type type) { return (type instanceof AlgebraicDataType) && ((AlgebraicDataType) type).getSymbol().equals(BuiltInTypeSymbol.SMT_TYPE); } @Override public Void visitClauseStmt(ClauseStmtContext ctx) { List head = termsToLiterals(ctx.clause().head.term()); List body = termsToLiterals(ctx.clause().body.term()); Set newRules = makeRules(ctx.start.getLine(), head, body); for (BasicRule rule : newRules) { RelationSymbol sym = rule.getHead().getSymbol(); if (!sym.isIdbSymbol()) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot define a rule for a non-IDB symbol: " + sym); } Util.lookupOrCreate(rules, sym, HashSet::new).add(rule); } return null; } private Set makeRules(int lineNo, ComplexLiteral head) { return makeRules(lineNo, Collections.singletonList(head), Collections.emptyList()); } private Set makeRules( int lineNo, List heads, List body) { List processedHeads = new ArrayList<>(); for (ComplexLiteral hd : heads) { if (hd instanceof UserPredicate) { processedHeads.add((UserPredicate) hd); } else { throw new UncheckedParseException( lineNo, "Cannot create rule with non-user predicate in head: " + hd); } } try { return varChecker.checkRule(processedHeads, body); } catch (VariableCheckPassException e) { throw new UncheckedParseException(lineNo, e.getMessage()); } } @Override public Void visitFactStmt(FactStmtContext ctx) { ComplexLiteral lit = termToLiteral(ctx.fact().term()); if (!(lit instanceof UserPredicate)) { throw new UncheckedParseException( ctx.start.getLine(), "Facts must be user-defined predicates: " + ctx.getText()); } UserPredicate fact = (UserPredicate) lit; RelationSymbol sym = fact.getSymbol(); if (sym.isIdbSymbol()) { Set rs = makeRules(ctx.start.getLine(), fact); rules.get(sym).addAll(rs); } else { try { Term[] args = varChecker.checkFact(fact.getArgs()); initialFacts.get(sym).add(args); } catch (VariableCheckPassException e) { throw new UncheckedParseException(ctx.start.getLine(), e.getMessage()); } } return null; } @Override public Void visitQueryStmt(QueryStmtContext ctx) { ComplexLiteral a = termToLiteral(ctx.query().term()); if (!(a instanceof UserPredicate)) { throw new UncheckedParseException( ctx.start.getLine(), "Query must be for a user-defined predicate: " + a); } if (query != null) { throw new UncheckedParseException( ctx.start.getLine(), "Cannot have multiple queries in the same program: " + query + " and " + a); } UserPredicate q = (UserPredicate) a; try { query = UserPredicate.make(q.getSymbol(), varChecker.checkFact(q.getArgs()), q.isNegated()); } catch (VariableCheckPassException e) { throw new UncheckedParseException( ctx.start.getLine(), "Problem with query " + query + ": " + e.getMessage()); } return null; } List termsToLiterals(Iterable ctxs) { List l = new ArrayList<>(); for (TermContext ctx : ctxs) { l.add(termToLiteral(ctx)); } return l; } private ComplexLiteral termToLiteral(TermContext ctx) { Term t = termExtractor.extract(ctx); if (!(t instanceof FunctionCall)) { return ComplexLiterals.unifyWithBool(t, true); } FunctionCall call = (FunctionCall) t; FunctionSymbol sym = call.getSymbol(); boolean negated = false; if (sym.equals(BuiltInFunctionSymbol.BNOT)) { t = call.getArgs()[0]; if (!(t instanceof FunctionCall)) { return ComplexLiterals.unifyWithBool(t, false); } negated = true; call = (FunctionCall) t; sym = call.getSymbol(); } if (sym instanceof PredicateFunctionSymbol) { RelationSymbol predSym = ((PredicateFunctionSymbol) sym).getPredicateSymbol(); return UserPredicate.make(predSym, call.getArgs(), negated); } if (!negated && sym.equals(BuiltInFunctionSymbol.BEQ)) { Term[] args = call.getArgs(); return UnificationPredicate.make(args[0], args[1], false); } if (!negated && sym.equals(BuiltInFunctionSymbol.BNEQ)) { Term[] args = call.getArgs(); return UnificationPredicate.make(args[0], args[1], true); } return ComplexLiterals.unifyWithBool(t, !negated); } public BasicProgram program() throws ParseException { return new BasicProgram() { @Override public Set getFunctionSymbols() { return pc.functionDefManager().getFunctionSymbols(); } @Override public Set getFactSymbols() { return Set.copyOf(initialFacts.keySet()); } @Override public Set getRuleSymbols() { return Set.copyOf(rules.keySet()); } @Override public FunctionDef getDef(FunctionSymbol sym) { return pc.functionDefManager().lookup(sym); } @Override public Set getFacts(RelationSymbol sym) { if (!sym.isEdbSymbol()) { throw new IllegalArgumentException(); } if (!initialFacts.containsKey(sym)) { throw new IllegalArgumentException(); } return initialFacts.get(sym); } @Override public Set getRules(RelationSymbol sym) { if (!sym.isIdbSymbol()) { throw new IllegalArgumentException(); } if (!rules.containsKey(sym)) { throw new IllegalArgumentException(); } return rules.get(sym); } @Override public SymbolManager getSymbolManager() { return pc.symbolManager(); } @Override public boolean hasQuery() { return query != null; } @Override public UserPredicate getQuery() { return query; } @Override public FunctionCallFactory getFunctionCallFactory() { return pc.functionCallFactory(); } @Override public Set getUninterpretedFunctionSymbols() { return Collections.unmodifiableSet(uninterpFuncSymbols); } @Override public Set getTypeSymbols() { return pc.symbolManager().getTypeSymbols(); } }; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/TypeExtractor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import static edu.harvard.seas.pl.formulog.util.Util.map; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogBaseVisitor; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.ParenTypeContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TupleTypeContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeRefContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogParser.TypeVarContext; import edu.harvard.seas.pl.formulog.parsing.generated.FormulogVisitor; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import java.util.ArrayList; import java.util.List; class TypeExtractor { private final ParsingContext pc; public TypeExtractor(ParsingContext parsingContext) { pc = parsingContext; } public Type extract(TypeContext ctx) { try { return ctx.accept(typeExtractor); } catch (UncheckedParseException e) { throw e; } catch (Exception e) { throw new UncheckedParseException(ctx.start.getLine(), e.getMessage()); } } public List extract(List ctxs) { List l = new ArrayList<>(); for (TypeContext ctx : ctxs) { l.add(extract(ctx)); } return l; } private final FormulogVisitor typeExtractor = new FormulogBaseVisitor() { @Override public Type visitTupleType(TupleTypeContext ctx) { List typeArgs = map(ctx.type0(), t -> t.accept(this)); if (typeArgs.size() == 1) { return typeArgs.get(0); } TypeSymbol sym = GlobalSymbolManager.lookupTupleTypeSymbol(typeArgs.size()); return AlgebraicDataType.make(sym, typeArgs); } @Override public Type visitTypeVar(TypeVarContext ctx) { return TypeVar.get(ctx.getText()); } @Override public Type visitTypeRef(TypeRefContext ctx) { List typeArgs; if (ctx.type0() != null) { typeArgs = new ArrayList<>(); typeArgs.add(ctx.type0().accept(this)); } else { typeArgs = map(ctx.type(), t -> t.accept(this)); } String s = ctx.ID().getText(); List params = ParsingUtil.extractParams(pc, ctx.parameterList()); switch (s) { case "i32": if (typeArgs.size() != 0) { throw new UncheckedParseException( ctx.start.getLine(), "Built in type i32 does not have any type parameters."); } return BuiltInTypes.i32; case "i64": if (typeArgs.size() != 0) { throw new UncheckedParseException( ctx.start.getLine(), "Built in type i64 does not have any type parameters."); } return BuiltInTypes.i64; case "fp32": if (typeArgs.size() != 0) { throw new UncheckedParseException( ctx.start.getLine(), "Built in type fp32 does not have any type parameters."); } return BuiltInTypes.fp32; case "fp64": if (typeArgs.size() != 0) { throw new UncheckedParseException( ctx.start.getLine(), "Built in type fp64 does not have any type parameters."); } return BuiltInTypes.fp64; case "string": if (typeArgs.size() != 0) { throw new UncheckedParseException( ctx.start.getLine(), "Built in type string does not have any type parameters."); } return BuiltInTypes.string; default: String name = ctx.ID().getText(); Symbol sym = pc.symbolManager().lookupSymbol(name); if (!(sym instanceof TypeSymbol)) { throw new UncheckedParseException(ctx.start.getLine(), "Not a type symbol: " + sym); } for (Param param : params) { typeArgs.add(param.getType()); } if (sym.equals(BuiltInTypeSymbol.FP) && typeArgs.size() == 1 && typeArgs.get(0) instanceof TypeIndex) { List expanded = ((TypeIndex) typeArgs.get(0)).expandAsFpIndex(); typeArgs.clear(); typeArgs.addAll(expanded); } if (sym.getArity() != typeArgs.size()) { var msg = "Type " + sym + " expects " + sym.getArity() + " parameter(s), but " + typeArgs.size() + " provided"; throw new UncheckedParseException(ctx.start.getLine(), msg); } return pc.typeManager().lookup((TypeSymbol) sym, typeArgs); } } @Override public Type visitParenType(ParenTypeContext ctx) { return ctx.type().accept(this); } }; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/UncheckedParseException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; public class UncheckedParseException extends RuntimeException { private static final long serialVersionUID = -4969126551201111362L; private final int lineNo; private final String fileName; public UncheckedParseException(int lineNo, String message) { super(message); this.lineNo = lineNo; this.fileName = null; } public UncheckedParseException(int lineNo, Throwable cause) { super(cause); this.lineNo = lineNo; this.fileName = null; } public UncheckedParseException(ParseException e) { super(e.getMessage()); this.lineNo = e.getLineNo(); this.fileName = e.getFileName(); } public int getLineNo() { return lineNo; } public String getFileName() { return fileName; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/VariableCheckPass.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.NestedFunctionDef; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.DummyFunctionDef; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.PredicateFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class VariableCheckPass { private final SymbolManager sm; public VariableCheckPass(SymbolManager sm) { this.sm = sm; } public Term checkFunction(Iterable arguments, Term body) throws VariableCheckPassException { PassContext ctx = new PassContext(arguments); Term newBody = ctx.checkTerm(body); ctx.checkCounts(); return newBody; } public Set checkRule(List heads, List body) throws VariableCheckPassException { PassContext ctx = new PassContext(); Set r = ctx.checkRule(heads, body); try { ctx.checkCounts(); } catch (VariableCheckPassException e) { String message = "Variable usage error in rule: " + e.getMessage() + "\n\n"; for (int i = 0; i < heads.size(); ++i) { message += heads.get(i); if (i < heads.size() - 1) { message += ",\n"; } } if (body.size() > 0) { message += " :-\n"; } for (int i = 0; i < body.size(); ++i) { message += "\t" + body.get(i); if (i < body.size() - 1) { message += ",\n"; } } message += "."; throw new VariableCheckPassException(message); } return r; } public Term[] checkFact(Term[] fact) throws VariableCheckPassException { PassContext ctx = new PassContext(); Term[] newArgs = new Term[fact.length]; for (int i = 0; i < fact.length; ++i) { newArgs[i] = ctx.checkTerm(fact[i]); } try { ctx.checkCounts(); } catch (VariableCheckPassException e) { throw new VariableCheckPassException("Variable usage error in fact: " + e.getMessage()); } return newArgs; } private class PassContext { private final Map cnts = new HashMap<>(); private final Set fresh = new HashSet<>(); public PassContext(Iterable seed) { for (Var x : seed) { int cnt = Util.lookupOrCreate(cnts, x, () -> 0); cnts.put(x, cnt + 1); } } public PassContext() {} public Set checkRule(List heads, List body) { List newBody = new ArrayList<>(); for (ComplexLiteral l : body) { newBody.add(checkLiteral(l)); } Set s = new HashSet<>(); for (UserPredicate head : heads) { head = (UserPredicate) checkLiteral(head); s.add(BasicRule.make(head, newBody)); } return s; } public ComplexLiteral checkLiteral(ComplexLiteral l) { Term[] newArgs = Terms.map(l.getArgs(), this::checkTerm); return l.accept( new ComplexLiteralVisitor() { @Override public ComplexLiteral visit(UnificationPredicate pred, Void input) { return UnificationPredicate.make(newArgs[0], newArgs[1], pred.isNegated()); } @Override public ComplexLiteral visit(UserPredicate pred, Void input) { return UserPredicate.make(pred.getSymbol(), newArgs, pred.isNegated()); } }, null); } public Term checkTerm(Term t) { return t.accept( new TermVisitor() { @Override public Term visit(Var x, Void in) { int cnt = Util.lookupOrCreate(cnts, x, () -> 0); cnts.put(x, cnt + 1); if (looksAnonymous(x)) { x = Var.fresh(); fresh.add(x); } return x; } @Override public Term visit(Constructor c, Void in) { Term[] newArgs = Terms.map(c.getArgs(), PassContext.this::checkTerm); return c.copyWithNewArgs(newArgs); } @Override public Term visit(Primitive p, Void in) { return p; } @Override public Term visit(Expr e, Void in) { return checkExpr(e); } }, null); } public Expr checkExpr(Expr e) { return e.accept( new ExprVisitor() { @Override public Expr visit(MatchExpr matchExpr, Void in) { Term scrutinee = checkTerm(matchExpr.getMatchee()); List clauses = new ArrayList<>(); for (MatchClause cl : matchExpr) { Term lhs = checkTerm(cl.getLhs()); Term rhs = checkTerm(cl.getRhs()); clauses.add(MatchClause.make(lhs, rhs)); } return MatchExpr.make(scrutinee, clauses); } @Override public Expr visit(FunctionCall funcCall, Void in) { Term[] newArgs = Terms.map(funcCall.getArgs(), PassContext.this::checkTerm); FunctionCallFactory factory = funcCall.getFactory(); FunctionSymbol sym = funcCall.getSymbol(); if (sym instanceof PredicateFunctionSymbol) { Pair p = updatePlaceholder((PredicateFunctionSymbol) sym, newArgs); sym = p.fst(); newArgs = p.snd(); FunctionDefManager dm = factory.getDefManager(); if (!dm.hasDefinition(sym)) { dm.register(new DummyFunctionDef(sym)); } } return factory.make(sym, newArgs); } @Override public Expr visit(LetFunExpr letFun, Void in) { Set defs = new HashSet<>(); for (NestedFunctionDef funcDef : letFun.getDefs()) { List newParams = new ArrayList<>(); for (Var x : funcDef.getParams()) { newParams.add((Var) checkTerm(x)); } Term newBody = checkTerm(funcDef.getBody()); defs.add(NestedFunctionDef.make(funcDef.getSymbol(), newParams, newBody)); } return LetFunExpr.make(defs, checkTerm(letFun.getLetBody())); } @Override public Expr visit(Fold fold, Void in) { FunctionCall f = (FunctionCall) fold.getShamCall().accept(this, in); return Fold.mk(f.getSymbol(), f.getArgs(), f.getFactory()); } }, null); } private Pair updatePlaceholder( PredicateFunctionSymbol placeholder, Term[] args) { BindingType[] bindings = new BindingType[args.length]; List argsToKeep = new ArrayList<>(); for (int i = 0; i < args.length; ++i) { Term arg = args[i]; if (arg instanceof Var) { Var x = (Var) arg; if (fresh.contains(x)) { bindings[i] = BindingType.IGNORED; } else if (looksLikeHole(x)) { bindings[i] = BindingType.FREE; cnts.put(x, cnts.get(x) - 1); } else { bindings[i] = BindingType.BOUND; argsToKeep.add(arg); } } else { bindings[i] = BindingType.BOUND; argsToKeep.add(arg); } } PredicateFunctionSymbol sym = sm.createPredicateFunctionSymbol(placeholder.getPredicateSymbol(), bindings); args = argsToKeep.toArray(Terms.emptyArray()); return new Pair<>(sym, args); } public void checkCounts() throws VariableCheckPassException { for (Map.Entry e : cnts.entrySet()) { Var x = e.getKey(); int cnt = e.getValue(); if (looksLikeHole(x) && cnt > 0) { throw new VariableCheckPassException( "Can only use hole ?? as an argument to a predicate aggregate function."); } else if (looksLikeQuasiAnonymousVar(x) && cnt > 1) { throw new VariableCheckPassException( "Quasi-anonymous variable " + x + " occurs more than once."); } else if (!looksAnonymous(x) && cnt == 1) { throw new VariableCheckPassException("Named variable " + x + " only occurs once."); } } } } private static boolean looksLikeHole(Var x) { return x.equals(Var.makeHole()); } private static boolean looksAnonymous(Var x) { return x.toString().startsWith("_") || x.toString().startsWith("$"); } private static boolean looksLikeTrueAnonymousVar(Var x) { return x.isUnderscore() || x.toString().startsWith("$"); } private static boolean looksLikeQuasiAnonymousVar(Var x) { return looksAnonymous(x) && !looksLikeTrueAnonymousVar(x); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/parsing/VariableCheckPassException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; public class VariableCheckPassException extends Exception { private static final long serialVersionUID = -4879050025193100459L; public VariableCheckPassException(String message) { super(message); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/AbstractSmtLibSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.Model; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractSmtLibSolver implements SmtLibSolver { protected static final Pair, Collection> emptyCollectionPair = new Pair<>(Collections.emptyList(), Collections.emptyList()); private static final ExternalSolverProcessFactory solverFactory; static { switch (Configuration.smtSolver) { case "z3": solverFactory = Z3ProcessFactory.get(); break; case "cvc4": solverFactory = Cvc4ProcessFactory.get(); break; case "yices": solverFactory = YicesProcessFactory.get(); break; case "boolector": solverFactory = BoolectorProcessFactory.get(); break; default: throw new AssertionError("impossible"); } } private static final AtomicInteger solverCnt = new AtomicInteger(); protected final int solverId = solverCnt.getAndIncrement(); protected SmtLibShim shim; protected Process solver; private final PrintWriter log; protected static int taskCnt; public AbstractSmtLibSolver() { PrintWriter w = null; if (Configuration.debugSmt) { try { Path path = Files.createDirectories(Paths.get(Configuration.debugSmtOutDir)); File log = path.resolve("solver" + solverId + ".log.smt2").toFile(); w = new PrintWriter(new FileWriter(log)); } catch (IOException e) { System.err.println("WARNING: Unable to create log for solver #" + solverId); } } log = w; instances.add(this); } protected abstract boolean isIncremental(); @Override public synchronized void start(Program prog) throws EvaluationException { assert solver == null; try { solver = solverFactory.newProcess(isIncremental()); } catch (IOException e) { throw new AssertionError("Could not create external solver process:\n" + e); } BufferedReader reader = new BufferedReader(new InputStreamReader(solver.getInputStream())); PrintWriter writer = new PrintWriter(solver.getOutputStream()); shim = new SmtLibShim(reader, writer, log); shim.initialize(prog, Configuration.smtDeclareAdts); start(); } @Override public synchronized void destroy() { while (solver != null && solver.isAlive()) { try { solver.getOutputStream().close(); solver.waitFor(); solver = null; } catch (Exception e) { e.printStackTrace(); } } } private static final Set instances = Util.concurrentSet(); public static synchronized void destroyAll() { for (var solver : instances) { solver.destroy(); } } @Override public void finalize() { instances.remove(this); destroy(); } protected abstract void start() throws EvaluationException; protected abstract Pair, Collection> makeAssertions( Collection assertions) throws EvaluationException; protected abstract void cleanup() throws EvaluationException; private SmtResult makeResult(SmtStatus status, Map m, int taskId) { Model model = m == null ? null : Model.make(m); return new SmtResult(status, model, solverId, taskId); } @Override public synchronized SmtResult check( Collection assertions, boolean getModel, int timeout) throws EvaluationException { assert solver != null; int taskId = taskCnt++; if (assertions.isEmpty()) { Map m = getModel ? Collections.emptyMap() : null; return makeResult(SmtStatus.SATISFIABLE, m, taskId); } String taskName = "#" + solverId + ":" + taskId + " (thread #" + Thread.currentThread().getId() + ")"; shim.printComment("*** START CALL " + taskName + " ***"); boolean debug = Configuration.timeSmt || log != null; long start = 0; if (debug) { start = System.nanoTime(); } Pair, Collection> p = makeAssertions(assertions); long encodeTime = 0; if (debug) { long end = System.nanoTime(); encodeTime = end - start; start = end; } try { SmtStatus status = shim.checkSatAssuming(p.fst(), p.snd(), timeout); if (debug) { long evalTime = System.nanoTime() - start; Configuration.recordSmtEvalTime(this, encodeTime, evalTime, status); if (log != null) { log.println("; time: " + evalTime / 1e6 + "ms"); log.flush(); } } Map m = null; if (status.equals(SmtStatus.SATISFIABLE) && getModel) { m = shim.getModel(); } cleanup(); shim.printComment("*** END CALL " + taskName + " ***\n"); return makeResult(status, m, taskId); } catch (EvaluationException e) { throw new EvaluationException("Problem with solver " + solverId + ":\n" + e.getMessage()); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/BestMatchSmtManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Collection; import java.util.Comparator; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerArray; public class BestMatchSmtManager implements SmtLibSolver { private final CheckSatAssumingSolver[] solvers; private final AtomicIntegerArray statuses; private static final int cacheCap = Configuration.smtCacheSize; public BestMatchSmtManager(int size) { solvers = new CheckSatAssumingSolver[size]; statuses = new AtomicIntegerArray(size); } @Override public SmtResult check(Collection conjuncts, boolean getModel, int timeout) throws EvaluationException { while (true) { PriorityQueue> q = new PriorityQueue<>(solvers.length, cmp); for (int i = 0; i < solvers.length; ++i) { double score = score(conjuncts, solvers[i]); q.add(new Pair<>(i, score)); } while (!q.isEmpty()) { int i = q.remove().fst(); if (statuses.compareAndSet(i, 0, 1)) { try { return solvers[i].check(conjuncts, getModel, timeout); } finally { statuses.set(i, 0); } } } } } private static final Comparator> cmp = new Comparator>() { @Override public int compare(Pair o1, Pair o2) { return Double.compare(o2.snd(), o1.snd()); } }; private double score(Collection conjuncts, CheckSatAssumingSolver solver) { Set cache = solver.getCache(); int cacheSize = cache.size(); if (cacheSize == 0) { return 0; } int hits = 0; for (SmtLibTerm conjunct : conjuncts) { if (cache.contains(conjunct)) { hits++; } } double score1 = 3 * hits / conjuncts.size(); double score2 = -((cacheSize - hits) / cacheCap); return score1 + score2; } @Override public void start(Program prog) throws EvaluationException { for (int i = 0; i < solvers.length; ++i) { CheckSatAssumingSolver solver = new CheckSatAssumingSolver(); solver.start(prog); solvers[i] = solver; } } @Override public void destroy() { for (SmtLibSolver solver : solvers) { solver.destroy(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/BoolectorProcessFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class BoolectorProcessFactory implements ExternalSolverProcessFactory { private static BoolectorProcessFactory instance; private BoolectorProcessFactory() { Util.assertBinaryOnPath("boolector"); } public static BoolectorProcessFactory get() { if (instance == null) { synchronized (BoolectorProcessFactory.class) { if (instance == null) { instance = new BoolectorProcessFactory(); } } } return instance; } @Override public Process newProcess(boolean incremental) throws IOException { List command = new ArrayList<>(); command.add("boolector"); command.add("--smt2"); if (incremental) { command.add("--incremental"); } return new ProcessBuilder(command).redirectErrorStream(true).start(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/CallAndResetSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Collection; public class CallAndResetSolver extends AbstractSmtLibSolver { @Override protected Pair, Collection> makeAssertions( Collection assertions) throws EvaluationException { for (SmtLibTerm assertion : assertions) { shim.makeAssertion(assertion); } if (Main.smtStats) { Configuration.smtCacheMisses.add(assertions.size()); Configuration.smtCacheClears.increment(); } return emptyCollectionPair; } @Override protected void cleanup() throws EvaluationException { shim.reset(); shim.makeDeclarations(); } @Override protected void start() throws EvaluationException { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); } @Override protected boolean isIncremental() { return false; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/CheckSatAssumingSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParamKind; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class CheckSatAssumingSolver extends AbstractSmtLibSolver { private final Map indicatorVars = new HashMap<>(); private int nextVarId; private void clearCache() throws EvaluationException { if (Configuration.timeSmt) { Configuration.recordCsaCacheClear(solverId); } if (Main.smtStats) { Configuration.smtCacheClears.increment(); } indicatorVars.clear(); nextVarId = 0; if (Configuration.smtCacheHardResets) { shim.reset(); start(); } else { shim.pop(); shim.push(); } } public Set getCache() { return indicatorVars.keySet(); } @Override protected Pair, Collection> makeAssertions( Collection formula) throws EvaluationException { int oldSize = indicatorVars.size(); int hits = 0; int misses = 0; Set onVars = new HashSet<>(); for (SmtLibTerm conjunct : formula) { SolverVariable x = indicatorVars.get(conjunct); if (x != null) { hits++; } else { misses++; x = makeIndicatorVar(conjunct); indicatorVars.put(conjunct, x); SmtLibTerm imp = makeImp(x, conjunct); shim.makeAssertion(imp); } onVars.add(x); } if (Configuration.timeSmt) { Configuration.recordCsaCacheStats(solverId, hits, misses, oldSize); } if (Main.smtStats) { Configuration.smtCacheHits.add(hits); Configuration.smtCacheMisses.add(misses); } Collection offVars; if (Configuration.smtUseNegativeLiterals) { offVars = indicatorVars.values().stream() .filter(x -> !onVars.contains(x)) .collect(Collectors.toList()); } else { offVars = Collections.emptySet(); } return new Pair<>(onVars, offVars); } private SmtLibTerm makeImp(SolverVariable x, SmtLibTerm assertion) { Term[] args = {x, assertion}; return Constructors.make(BuiltInConstructorSymbol.SMT_IMP, args); } private SolverVariable makeIndicatorVar(SmtLibTerm assertion) { Term[] args = Terms.singletonArray(Terms.makeDummyTerm(nextVarId++)); ParameterizedConstructorSymbol sym = GlobalSymbolManager.getParameterizedSymbol(BuiltInConstructorSymbolBase.SMT_VAR); sym = sym.copyWithNewArgs(Param.wildCard(), new Param(BuiltInTypes.bool, ParamKind.PRE_SMT_TYPE)); return (SolverVariable) Constructors.make(sym, args); } @Override protected void cleanup() throws EvaluationException { if (indicatorVars.size() > Configuration.smtCacheSize) { clearCache(); } } @Override protected void start() throws EvaluationException { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); if (!Configuration.smtCacheHardResets) { shim.push(); } } @Override protected boolean isIncremental() { return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/Cvc4ProcessFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class Cvc4ProcessFactory implements ExternalSolverProcessFactory { private static Cvc4ProcessFactory instance; private Cvc4ProcessFactory() { Util.assertBinaryOnPath("cvc4"); } public static Cvc4ProcessFactory get() { if (instance == null) { synchronized (Cvc4ProcessFactory.class) { if (instance == null) { instance = new Cvc4ProcessFactory(); } } } return instance; } @Override public Process newProcess(boolean incremental) throws IOException { List command = new ArrayList<>(); command.add("cvc4"); command.add("--lang"); command.add("smt"); if (incremental) { command.add("--incremental"); } return new ProcessBuilder(command).redirectErrorStream(true).start(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/DoubleCheckingSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.Collection; public class DoubleCheckingSolver implements SmtLibSolver { private final SmtLibSolver inner; private final SmtLibSolver checker = new PushPopSolver(); public DoubleCheckingSolver(SmtLibSolver inner) { this.inner = inner; } @Override public void start(Program prog) throws EvaluationException { inner.start(prog); checker.start(prog); } @Override public SmtResult check(Collection formula, boolean getModel, int timeout) throws EvaluationException { SmtResult res = inner.check(formula, getModel, timeout); if (res.status.equals(SmtStatus.UNKNOWN)) { SmtResult res2 = checker.check(formula, getModel, timeout); if (Configuration.timeSmt) { Configuration.recordSmtDoubleCheck(!res2.status.equals(SmtStatus.UNKNOWN)); } res = res2; } return res; } @Override public void destroy() { inner.destroy(); checker.destroy(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/ExternalSolverProcessFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import java.io.IOException; public interface ExternalSolverProcessFactory { Process newProcess(boolean incremental) throws IOException; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/NotThreadSafeQueueSmtManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.Collection; import java.util.function.Supplier; public class NotThreadSafeQueueSmtManager implements SmtLibSolver { private final SmtLibSolver[] solvers; private int pos; private final Supplier maker; public NotThreadSafeQueueSmtManager(int size, Supplier maker) { if (size <= 0) { throw new IllegalArgumentException("Cannot have non-positive number of solvers."); } solvers = new SmtLibSolver[size]; this.maker = maker; } @Override public SmtResult check(Collection conjuncts, boolean getModel, int timeout) throws EvaluationException { SmtLibSolver solver = solvers[pos]; SmtResult res = solver.check(conjuncts, getModel, timeout); pos = (pos + 1) % solvers.length; return res; } @Override public void start(Program prog) throws EvaluationException { for (int i = 0; i < solvers.length; ++i) { SmtLibSolver solver = maker.get(); solver.start(prog); solvers[i] = solver; } } @Override public void destroy() { for (SmtLibSolver solver : solvers) { solver.destroy(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/PerThreadSmtManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.eval.UncheckedEvaluationException; import java.util.Collection; import java.util.function.Supplier; public class PerThreadSmtManager implements SmtLibSolver { private final ThreadLocal subManager; private volatile Program prog; public PerThreadSmtManager(Supplier managerMaker) { subManager = new ThreadLocal() { @Override protected SmtLibSolver initialValue() { SmtLibSolver m = managerMaker.get(); try { m.start(prog); } catch (EvaluationException e) { throw new UncheckedEvaluationException(e.getMessage()); } return m; } }; } @Override public SmtResult check(Collection conjuncts, boolean getModel, int timeout) throws EvaluationException { try { return subManager.get().check(conjuncts, getModel, timeout); } catch (UncheckedEvaluationException e) { throw new EvaluationException(e.getMessage()); } } @Override public void start(Program prog) throws EvaluationException { this.prog = prog; } @Override public void destroy() { subManager.get().destroy(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/PushPopNaiveSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Collection; public class PushPopNaiveSolver extends AbstractSmtLibSolver { @Override protected boolean isIncremental() { return true; } @Override protected void start() throws EvaluationException { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); } @Override protected Pair, Collection> makeAssertions( Collection assertions) throws EvaluationException { shim.push(); for (SmtLibTerm assertion : assertions) { shim.makeAssertion(assertion); } if (Main.smtStats) { Configuration.smtCacheMisses.add(assertions.size()); Configuration.smtCacheClears.increment(); } return emptyCollectionPair; } @Override protected void cleanup() throws EvaluationException { shim.pop(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/PushPopSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import java.util.Iterator; public class PushPopSolver extends AbstractSmtLibSolver { private final Deque cache = new ArrayDeque<>(); @Override protected Pair, Collection> makeAssertions( Collection assertions) throws EvaluationException { int baseSize = cache.size(); int i = findDiffPos(assertions); if (Main.smtStats) { Configuration.smtCacheHits.add(i); if (i == 0 && !cache.isEmpty()) { Configuration.smtCacheClears.increment(); } Configuration.smtCacheMisses.add(assertions.size() - i); } int pops = baseSize - i; shrinkCache(i); Iterator it = assertions.iterator(); for (int j = 0; j < i; ++j) { it.next(); } growCache(it); if (Configuration.timeSmt) { Configuration.recordPushPopSolverStats(solverId, baseSize, pops, cache.size() - i); } return emptyCollectionPair; } private int findDiffPos(Collection assertions) { int i = 0; Iterator cacheIt = cache.iterator(); for (SmtLibTerm assertion : assertions) { if (!cacheIt.hasNext()) { break; } SmtLibTerm cached = cacheIt.next(); if (!cached.equals(assertion)) { break; } ++i; } return i; } private void shrinkCache(int tgtSize) throws EvaluationException { int size = cache.size(); shim.pop(size - tgtSize); while (size > tgtSize) { cache.removeLast(); --size; } } private void growCache(Iterator assertions) throws EvaluationException { while (assertions.hasNext()) { SmtLibTerm assertion = assertions.next(); shim.push(); shim.makeAssertion(assertion); cache.addLast(assertion); } } @Override protected void cleanup() { // Do nothing } @Override protected void start() throws EvaluationException { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); } @Override protected boolean isIncremental() { return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/QueueSmtManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; import java.util.function.Supplier; public class QueueSmtManager implements SmtLibSolver { private final ArrayBlockingQueue solvers; private final Supplier maker; public QueueSmtManager(int size, Supplier maker) { if (size <= 0) { throw new IllegalArgumentException("Cannot have non-positive number of solvers."); } solvers = new ArrayBlockingQueue<>(size); this.maker = maker; } @Override public SmtResult check(Collection conjuncts, boolean getModel, int timeout) throws EvaluationException { SmtLibSolver solver; try { solver = solvers.take(); } catch (InterruptedException e) { throw new EvaluationException(e); } SmtResult res = solver.check(conjuncts, getModel, timeout); solvers.add(solver); return res; } @Override public void start(Program prog) throws EvaluationException { while (solvers.remainingCapacity() > 0) { SmtLibSolver solver = maker.get(); solver.start(prog); solvers.add(solver); } } @Override public void destroy() { for (SmtLibSolver solver : solvers) { solver.destroy(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SingleShotSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.util.Pair; import java.util.Collection; public class SingleShotSolver extends AbstractSmtLibSolver { private Program prog; @Override protected Pair, Collection> makeAssertions( Collection assertions) throws EvaluationException { shim.setLogic(Configuration.smtLogic); shim.makeDeclarations(); for (SmtLibTerm assertion : assertions) { shim.makeAssertion(assertion); } if (Main.smtStats) { Configuration.smtCacheMisses.add(assertions.size()); Configuration.smtCacheClears.increment(); } return emptyCollectionPair; } @Override protected void start() throws EvaluationException { // do nothing } @Override protected void cleanup() throws EvaluationException { destroy(); super.start(prog); } public synchronized void start(Program prog) throws EvaluationException { this.prog = prog; super.start(prog); } @Override protected boolean isIncremental() { return false; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtLibParser.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.BoolTerm; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.FP32; import edu.harvard.seas.pl.formulog.ast.FP64; import edu.harvard.seas.pl.formulog.ast.I32; import edu.harvard.seas.pl.formulog.ast.I64; import edu.harvard.seas.pl.formulog.ast.StringTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import java.io.IOException; import java.io.Reader; import java.io.StreamTokenizer; import java.io.StringReader; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class SmtLibParser { private final SymbolManager symbolManager; private final Map variables; public SmtLibParser(SymbolManager symbolManager, Map variables) { this.symbolManager = symbolManager; this.variables = variables; } public Map getModel(Reader r) throws IOException, SmtLibParseException { Tokenizer t = new Tokenizer(r); t.consume("("); Map m = new HashMap<>(); while (!t.peek().equals(")")) { if (t.peek().equals(";")) { consumeComment(t); } else { parseFunctionDef(m, t); } } t.consume(")"); t.ignoreWhitespace(false); // Remove EOL t.next(); return m; } private void consumeComment(Tokenizer t) throws IOException, SmtLibParseException { t.consume(";;"); t.ignoreWhitespace(false); while (!t.next().equals("\n")) { // do nothing } t.ignoreWhitespace(true); } private void parseFunctionDef(Map m, Tokenizer t) throws IOException, SmtLibParseException { t.consume("("); if (t.peek().equals("forall") || t.peek().equals("declare")) { skipRestOfSExp(t); return; } t.consume("define-fun"); if (t.peek().equals("-")) { skipRestOfSExp(t); return; } String id = parseIdentifier(t); // Ignore args t.consume("("); skipRestOfSExp(t); // Parse type parseType(t); SolverVariable x = variables.get(id); if (x != null) { FunctorType ft = (FunctorType) x.getSymbol().getCompileTimeType(); AlgebraicDataType type = (AlgebraicDataType) ft.getRetType(); type = stripSymType(type); if (shouldRecord(type)) { m.put(x, parseTerm(t, type)); } } skipRestOfSExp(t); } private static enum TermType { BV32, BV64, FP32, FP64, STRING, ADT } public static AlgebraicDataType stripSymType(AlgebraicDataType symType) { assert symType.getSymbol().equals(BuiltInTypeSymbol.SYM_TYPE); return (AlgebraicDataType) symType.getTypeArgs().get(0); } public static boolean shouldRecord(AlgebraicDataType type) throws SmtLibParseException { Set seen = new HashSet<>(); boolean ok = shouldRecord1(type, seen); for (Type arg : type.getTypeArgs()) { if (arg instanceof AlgebraicDataType) { ok &= shouldRecord1((AlgebraicDataType) arg, seen); } } return ok; } private static void die(String msg) throws SmtLibParseException { throw new SmtLibParseException("INTERNAL ERROR: " + msg); } private static boolean shouldRecord1(AlgebraicDataType type, Set seen) throws SmtLibParseException { TypeSymbol sym = type.getSymbol(); if (!seen.add(sym)) { return true; } if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case BOOL_TYPE: case CMP_TYPE: case LIST_TYPE: case OPTION_TYPE: case STRING_TYPE: return true; case ARRAY_TYPE: case INT_TYPE: return false; case BV: TypeIndex idx = (TypeIndex) type.getTypeArgs().get(0); int w = idx.getIndex(); return w == 32 || w == 64; case FP: TypeIndex idx1 = (TypeIndex) type.getTypeArgs().get(0); int e = idx1.getIndex(); TypeIndex idx2 = (TypeIndex) type.getTypeArgs().get(1); int s = idx2.getIndex(); return (e == 8 && s == 24) || (e == 11 && s == 53); case SMT_TYPE: case MODEL_TYPE: case SYM_TYPE: default: die("unexpected built-in symbol: " + sym); } } if (sym.isUninterpretedSort()) { return false; } boolean ok = true; if (type.hasConstructors()) { for (ConstructorScheme cs : type.getConstructors()) { for (Type ty : cs.getTypeArgs()) { if (ty instanceof AlgebraicDataType) { ok &= shouldRecord1((AlgebraicDataType) ty, seen); } } } } return ok; } private TermType getTermType(AlgebraicDataType type) throws SmtLibParseException { TypeSymbol sym = type.getSymbol(); if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case BOOL_TYPE: case CMP_TYPE: case LIST_TYPE: case OPTION_TYPE: return TermType.ADT; case STRING_TYPE: return TermType.STRING; case BV: { TypeIndex idx = (TypeIndex) type.getTypeArgs().get(0); int w = idx.getIndex(); if (w == 32) { return TermType.BV32; } else if (w == 64) { return TermType.BV64; } die("unexpected BV width " + w); } case FP: { TypeIndex idx1 = (TypeIndex) type.getTypeArgs().get(0); int e = idx1.getIndex(); TypeIndex idx2 = (TypeIndex) type.getTypeArgs().get(1); int s = idx2.getIndex(); if (e == 8 && s == 24) { return TermType.FP32; } else if (e == 11 && s == 53) { return TermType.FP64; } die("unexpected FP dimensions " + e + "/" + s); } default: die("unexpected built-in symbol: " + sym); } } return TermType.ADT; } private String parseIdentifier(Tokenizer t) throws IOException, SmtLibParseException { String s = t.next(); if (s.equals("|")) { s = ""; while (!t.peek().equals("|")) { s += t.next(); } } else { if (t.peek().equals("!")) { t.consume("!"); s += "!"; s += parseIdentifier(t); } } return s; } private void parseType(Tokenizer t) throws IOException, SmtLibParseException { String s = t.next(); if (s.equals("(")) { skipRestOfSExp(t); } } private long parseBv(Tokenizer t) throws IOException, SmtLibParseException { t.consume("#"); String tok = t.next(); String prefix = tok.substring(0, 1); String num = tok.substring(1); int base = 0; if (prefix.equals("b")) { base = 2; } else { assert (prefix.equals("x")); base = 16; } return Long.parseUnsignedLong(num, base); } private Term parseTerm(Tokenizer t, AlgebraicDataType type) throws IOException, SmtLibParseException { switch (getTermType(type)) { case ADT: return parseADTTerm(t, type); case BV32: { return I32.make((int) parseBv(t)); } case BV64: { return I64.make(parseBv(t)); } // FIXME I'm not sure if these conversions to floating point are 100% // correct... case FP32: { float val = -1; t.consume("("); if (t.peek().equals("fp")) { t.consume("fp"); long sign = parseBv(t); long exp = parseBv(t); long mant = parseBv(t); long bits = sign << 31; bits |= exp << 23; bits |= mant; val = Float.intBitsToFloat((int) bits); } else { t.consume("_"); String next = t.next(); if (next.equals("NaN")) { val = Float.NaN; } else if (next.equals("+")) { if (t.peek().equals("oo")) { t.consume("oo"); val = Float.POSITIVE_INFINITY; } else { t.consume("zero"); val = +0.0f; } } else { assert next.equals("-"); if (t.peek().equals("oo")) { t.consume("oo"); val = Float.NEGATIVE_INFINITY; } else { t.consume("zero"); val = -0.0f; } } } skipRestOfSExp(t); return FP32.make(val); } case FP64: { double val = -1; t.consume("("); if (t.peek().equals("fp")) { t.consume("fp"); long sign = parseBv(t); long exp = parseBv(t); long mant = parseBv(t); long bits = sign << 63; bits |= exp << 52; bits |= mant; val = Double.longBitsToDouble(bits); } else { t.consume("_"); String next = t.next(); if (next.equals("NaN")) { val = Double.NaN; } else if (next.equals("+")) { if (t.peek().equals("oo")) { t.consume("oo"); val = Double.POSITIVE_INFINITY; } else { t.consume("zero"); val = +0.0; } } else { assert next.equals("-"); if (t.peek().equals("oo")) { t.consume("oo"); val = Double.NEGATIVE_INFINITY; } else { t.consume("zero"); val = -0.0; } } } skipRestOfSExp(t); return FP64.make(val); } case STRING: return parseString(t); } die("unexpected term type: " + getTermType(type)); return null; } private Term parseString(Tokenizer t) throws IOException, SmtLibParseException { t.consume("\""); t.ignoreWhitespace(false); StringBuilder sb = new StringBuilder(); while (true) { String next = t.next(); if (next.equals("\"")) { // SMT-LIB uses "" to represent the character " if (!t.peek().equals("\"")) { break; } t.consume("\""); } sb.append(next); } t.ignoreWhitespace(true); return StringTerm.make(sb.toString()); } private Term parseADTTerm(Tokenizer t, AlgebraicDataType type) throws IOException, SmtLibParseException { String id = t.next(); if (id.equals("(")) { id = t.next(); if (id.equals("as")) { Term term = parseADTTerm(t, type); skipRestOfSExp(t); return term; } } if (id.equals("true")) { return BoolTerm.mkTrue(); } if (id.equals("false")) { return BoolTerm.mkFalse(); } ConstructorSymbol sym = (ConstructorSymbol) symbolManager.lookupSymbol(id); Term[] args = Terms.emptyArray(); if (sym.getArity() > 0) { List argTypes = null; for (ConstructorScheme cs : type.getConstructors()) { if (cs.getSymbol().equals(sym)) { argTypes = cs.getTypeArgs(); break; } } assert argTypes != null; args = new Term[argTypes.size()]; int i = 0; for (Type ty : argTypes) { Term arg = parseTerm(t, (AlgebraicDataType) ty); args[i] = arg; ++i; } skipRestOfSExp(t); } return Constructors.make(sym, args); } private void skipRestOfSExp(Tokenizer t) throws IOException, SmtLibParseException { int depth = 0; while (depth >= 0) { switch (t.next()) { case "(": depth++; break; case ")": depth--; break; } } } private static class Tokenizer { private final StreamTokenizer t; public Tokenizer(Reader r) { t = new StreamTokenizer(r); t.ordinaryChar('.'); t.ordinaryChar('-'); t.ordinaryChars('0', '9'); t.ordinaryChar('"'); t.ordinaryChar('\''); t.ordinaryChar('/'); t.wordChars('0', '9'); t.wordChars('_', '_'); } // FIXME This is almost certainly incomplete. public void ignoreWhitespace(boolean ignore) { if (ignore) { t.whitespaceChars('\n', '\n'); t.whitespaceChars('\r', '\r'); t.whitespaceChars('\t', '\t'); t.whitespaceChars(' ', ' '); } else { t.ordinaryChar('\n'); t.ordinaryChar('\r'); t.ordinaryChar('\t'); t.ordinaryChar(' '); } } public String peek() throws IOException, SmtLibParseException { String token = next(); t.pushBack(); return token; } public String next() throws IOException, SmtLibParseException { int token = t.nextToken(); switch (t.ttype) { case StreamTokenizer.TT_EOF: throw new SmtLibParseException("Unexpected EOF."); case StreamTokenizer.TT_EOL: // This should only happen in the mode when whitespace is significant. return "\n"; case StreamTokenizer.TT_NUMBER: die("nothing should be tokenized as a number."); case StreamTokenizer.TT_WORD: return t.sval; default: return Character.toString((char) token); } } public boolean hasNext() throws IOException { t.nextToken(); t.pushBack(); return t.ttype != StreamTokenizer.TT_EOF; } public void consume(String s) throws IOException, SmtLibParseException { Tokenizer t = new Tokenizer(new StringReader(s)); while (t.hasNext()) { String expected = t.next(); String found = next(); if (!expected.equals(found)) { throw new SmtLibParseException( "Tried to consume \"" + expected + "\", but found \"" + found + "\"."); } } } } @SuppressWarnings("serial") public static class SmtLibParseException extends Exception { public SmtLibParseException(String message) { super(message); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtLibShim.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Constructors.SolverVariable; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.smt.SmtLibParser.SmtLibParseException; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.SymbolManager; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.Types; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.OpaqueType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.types.Types.TypeVisitor; import edu.harvard.seas.pl.formulog.util.DedupWorkList; import edu.harvard.seas.pl.formulog.util.Pair; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.apache.commons.lang3.time.StopWatch; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.KosarajuStrongConnectivityInspector; import org.jgrapht.alg.interfaces.StrongConnectivityAlgorithm; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.traverse.TopologicalOrderIterator; public class SmtLibShim { private static final boolean recordTime = Configuration.timeSmt; private final BufferedReader in; private PrintWriter out; private final Map declaredSymbols = new HashMap<>(); private final Deque> symbolsByStackPos = new ArrayDeque<>(); private final Map symbolLookup = new HashMap<>(); private PrintWriter log; private Iterator> typeAnnotations; private SymbolManager symbolManager; private final List declarations = new ArrayList<>(); public SmtLibShim(Reader in, Writer out) { this(in, out, null); } public SmtLibShim(Reader in, Writer out, Writer log) { this.in = in != null ? new BufferedReader(in) : null; this.out = new PrintWriter(out); this.log = log != null ? new PrintWriter(log) : null; symbolsByStackPos.add(new HashSet<>()); } public void initialize(Program prog, boolean declareAdts) { symbolManager = prog.getSymbolManager(); new DeclarationGatherer(declareAdts).go(prog); if (Configuration.smtCheckSuccess) { println("(set-option :print-success true)"); } try { checkSuccess(); } catch (EvaluationException e) { throw new AssertionError(e); } } private void checkSuccess() throws EvaluationException { if (in != null && Configuration.smtCheckSuccess) { flush(); try { String r = in.readLine(); if (log != null) { log.println("; success? " + r); log.flush(); } if (r == null || !r.equals("success")) { throw new EvaluationException("Solver did not return success: " + r); } } catch (IOException e) { throw new EvaluationException("Problem with evaluating solver: " + e.getMessage()); } } } public void makeAssertion(SmtLibTerm assertion) throws EvaluationException { long start = 0; long end = 0; if (recordTime) { start = System.nanoTime(); } declareSymbols(assertion); if (recordTime) { end = System.nanoTime(); Configuration.recordSmtDeclTime(end - start); start = end; } typeAnnotations = new MiniTypeInferer().inferTypes(assertion).iterator(); if (recordTime) { end = System.nanoTime(); Configuration.recordSmtInferTime(end - start); start = end; } print("(assert "); assertion.toSmtLib(this); println(")"); checkSuccess(); if (recordTime) { end = System.nanoTime(); Configuration.recordSmtSerialTime(end - start); } assert !typeAnnotations.hasNext() : typeAnnotations.next(); } public void reset() throws EvaluationException { declaredSymbols.clear(); symbolLookup.clear(); symbolsByStackPos.clear(); symbolsByStackPos.add(new HashSet<>()); println("(reset)"); checkSuccess(); } public void resetAssertions() throws EvaluationException { println("(reset-assertions)"); checkSuccess(); } public void push() throws EvaluationException { // Avoid push timing out. See setTimeout(Integer.MAX_VALUE); println("(push 1)"); checkSuccess(); symbolsByStackPos.addLast(new HashSet<>()); } public void pop() throws EvaluationException { pop(1); } public void pop(int n) throws EvaluationException { println("(pop " + n + ")"); checkSuccess(); for (int i = 0; i < n; ++i) { for (SolverVariable x : symbolsByStackPos.removeLast()) { String s = declaredSymbols.remove(x); symbolLookup.remove(s); } } } public SmtStatus checkSat(int timeout) throws EvaluationException { return checkSatAssuming(Collections.emptyList(), Collections.emptyList(), timeout); } private void setTimeout(int timeout) throws EvaluationException { if (timeout < 0) { System.err.println("Warning: negative timeout provided to solver - ignored"); timeout = Integer.MAX_VALUE; } if (Configuration.smtSolver.equals("z3")) { println("(set-option :timeout " + timeout + ")"); checkSuccess(); } } public SmtStatus checkSatAssuming( Collection onVars, Collection offVars, int timeout) throws EvaluationException { setTimeout(timeout); if (onVars.isEmpty() && offVars.isEmpty()) { println("(check-sat)"); } else { print("(check-sat-assuming ("); for (SolverVariable x : onVars) { print(x); print(" "); } for (SolverVariable x : offVars) { print("(not "); print(x); print(") "); } println("))"); } flush(); String result; try { StopWatch clock = null; if (Main.smtStats) { clock = new StopWatch(); clock.start(); } result = in.readLine(); if (Main.smtStats) { Configuration.smtTime.get().add(clock.getTime()); } if (result == null) { throw new EvaluationException("Problem with evaluating solver! Unexpected end of stream"); } if (log != null) { log.println("; result: " + result); log.flush(); } if (result.equals("sat")) { return SmtStatus.SATISFIABLE; } else if (result.equals("unsat")) { return SmtStatus.UNSATISFIABLE; } else if (result.equals("unknown")) { return SmtStatus.UNKNOWN; } else { throw new EvaluationException( "Problem with evaluating solver! Unexpected result: " + result); } } catch (IOException e) { throw new EvaluationException("Problem with evaluating solver: " + e.getMessage()); } } public Map getModel() throws EvaluationException { println("(get-model)"); flush(); try { return parseModel(); } catch (IOException e) { throw new EvaluationException("Problem with evaluating solver: " + e.getMessage()); } catch (SmtLibParseException e) { throw new EvaluationException("Problem parsing solver output: " + e.getMessage()); } } public Map parseModel() throws EvaluationException, IOException, SmtLibParseException { SmtLibParser p = new SmtLibParser(symbolManager, symbolLookup); return p.getModel(in); } public void flush() { out.flush(); if (log != null) { log.flush(); } } public void setLogic(String logic) throws EvaluationException { println("(set-logic " + logic + ")"); checkSuccess(); } public void print(String s) { out.print(s); if (log != null) { log.print(s); } } private void println(String s) { print(s); print("\n"); } public void printComment(String comment) { println("; " + comment); } public void print(SolverVariable x) { String s = declaredSymbols.get(x); if (s == null) { throw new NoSuchElementException(x.toString()); } print(s); } public void print(Symbol sym) { print(stringifySymbol(sym)); } public void print(Type type) { print(stringifyType(type)); } public String getTypeAnnotation(ConstructorSymbol sym) { if (needsTypeAnnotation(sym)) { Pair p = typeAnnotations.next(); assert p.fst().equals(sym); return stringifyType(p.snd()); } return null; } private String toSmtSymbol(SolverVariable x) { return "x" + x.getSolverVarId(); } private void declareSymbols(SmtLibTerm t) throws EvaluationException { t.accept( new TermVisitorExn() { @Override public Void visit(Var t, Void in) { throw new AssertionError("impossible"); } @Override public Void visit(Constructor c, Void in) throws EvaluationException { if (c instanceof SolverVariable) { SolverVariable var = (SolverVariable) c; if (!declaredSymbols.containsKey(var)) { String s = toSmtSymbol(var); declaredSymbols.put(var, s); symbolLookup.put(s, var); symbolsByStackPos.getLast().add(var); print("(declare-fun " + s + " () "); FunctorType ft = (FunctorType) var.getSymbol().getCompileTimeType(); print(stringifyType(ft.getRetType())); println(")"); checkSuccess(); } return null; } for (Term arg : c.getArgs()) { arg.accept(this, in); } return null; } @Override public Void visit(Primitive p, Void in) { return null; } @Override public Void visit(Expr expr, Void in) { throw new AssertionError("impossible"); } }, null); } public void makeDeclarations() { long start = 0; if (Configuration.timeSmt) { start = System.nanoTime(); } for (String decl : declarations) { println(decl); try { checkSuccess(); } catch (EvaluationException e) { System.err.println( "WARNING: solver rejected declaration:\n" + decl + "\n" + e.getMessage()); } } if (Configuration.timeSmt) { Configuration.recordSmtDeclGlobalsTime(System.nanoTime() - start); } } private class DeclarationGatherer { private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); private final boolean declareAdts; public DeclarationGatherer(boolean declareAdts) { this.declareAdts = declareAdts; } public void go(Program prog) { PrintWriter tmpLog = log; log = null; PrintWriter tmpOut = out; out = new PrintWriter(baos); declareSorts(prog.getTypeSymbols()); declareUninterpretedFunctions(prog.getUninterpretedFunctionSymbols()); out = tmpOut; log = tmpLog; } private void pushDeclaration() { flush(); declarations.add(baos.toString()); baos.reset(); } private void declareUninterpretedFunctions(Set funcs) { for (ConstructorSymbol func : funcs) { print("(declare-fun " + stringifySymbol(func) + " ("); FunctorType ft = func.getCompileTimeType(); for (Iterator it = ft.getArgTypes().iterator(); it.hasNext(); ) { print(stringifyType(it.next())); if (it.hasNext()) { print(" "); } } print(") " + stringifyType(ft.getRetType()) + ")"); pushDeclaration(); } } private void declareSorts(Set sorts) { SortDependencyFinder depends = new SortDependencyFinder(sorts); StrongConnectivityAlgorithm k = new KosarajuStrongConnectivityInspector<>(depends.compute()); TopologicalOrderIterator, DefaultEdge> topo = new TopologicalOrderIterator<>(k.getCondensation()); while (topo.hasNext()) { Graph scc = topo.next(); declareScc(scc.vertexSet()); } } private void declareScc(Set sorts) { assert !sorts.isEmpty(); TypeSymbol sym = sorts.iterator().next(); if (sym.isUninterpretedSort()) { assert sorts.size() == 1; declareUninterpretedSort(sym); } else if (declareAdts) { assert sym.isNormalType(); declareAdtSorts(sorts); } } private void declareUninterpretedSort(TypeSymbol sort) { assert sort.isUninterpretedSort(); print("(declare-sort " + stringifySymbol(sort) + " " + sort.getArity() + ")"); pushDeclaration(); } private void declareAdtSorts(Set sorts) { assert !sorts.isEmpty(); print("(declare-datatypes ( "); for (TypeSymbol sym : sorts) { assert sym.isNormalType(); print("(" + stringifySymbol(sym) + " " + sym.getArity() + ") "); } print(") ("); for (TypeSymbol sym : sorts) { declareAdtSort(AlgebraicDataType.makeWithFreshArgs(sym)); } print("))"); pushDeclaration(); } private void declareAdtSort(AlgebraicDataType type) { print("\n (par ("); for (Iterator it = type.getTypeArgs().iterator(); it.hasNext(); ) { print(stringifyType(it.next())); if (it.hasNext()) { print(" "); } } print(") ("); for (ConstructorScheme c : type.getConstructors()) { declareConstructor(c); } print("))"); } private void declareConstructor(ConstructorScheme c) { print("\n ("); print(stringifySymbol(c.getSymbol())); Iterator getterSyms = c.getGetterSymbols().iterator(); for (Type t : c.getTypeArgs()) { String getter = stringifySymbol(getterSyms.next()); print(" (" + getter + " " + stringifyType(t) + ")"); } print(")"); } } private String stringifySymbol(Symbol sym) { return "|" + sym + "|"; } private String stringifyType(Type type) { return type.accept( new TypeVisitor() { @Override public String visit(TypeVar typeVar, Void in) { return "|" + typeVar + "|"; } @Override public String visit(AlgebraicDataType algebraicType, Void in) { TypeSymbol sym = algebraicType.getSymbol(); if (sym instanceof BuiltInTypeSymbol) { List typeArgs = algebraicType.getTypeArgs(); switch ((BuiltInTypeSymbol) sym) { case BOOL_TYPE: return "Bool"; case STRING_TYPE: return "String"; case ARRAY_TYPE: { String s = "(Array "; for (Type t : typeArgs) { s += " " + stringifyType(t); } return s + ")"; } case INT_TYPE: return "Int"; case SMT_TYPE: case SYM_TYPE: return stringifyType(algebraicType.getTypeArgs().get(0)); case BV: return "(_ BitVec " + ((TypeIndex) typeArgs.get(0)).getIndex() + ")"; case FP: int idx1 = ((TypeIndex) typeArgs.get(0)).getIndex(); int idx2 = ((TypeIndex) typeArgs.get(1)).getIndex(); return "(_ FloatingPoint " + idx1 + " " + idx2 + ")"; case CMP_TYPE: case LIST_TYPE: case OPTION_TYPE: break; case MODEL_TYPE: case SMT_PATTERN_TYPE: case SMT_WRAPPED_VAR_TYPE: default: return null; } } if (sym.getArity() == 0) { return stringifySymbol(sym); } String s = "(" + stringifySymbol(sym); for (Type t : algebraicType.getTypeArgs()) { s += " " + stringifyType(t); } return s + ")"; } @Override public String visit(OpaqueType opaqueType, Void in) { throw new AssertionError("impossible"); } @Override public String visit(TypeIndex typeIndex, Void in) { throw new AssertionError("shouldn't happen"); } }, null); } private class SortDependencyFinder { private final DedupWorkList w = new DedupWorkList<>(); public SortDependencyFinder(Set types) { for (TypeSymbol type : types) { push(type); } } private void push(TypeSymbol sym) { if (isDeclarableTypeSymbol(sym)) { w.push(sym); } } private Graph compute() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); while (!w.isEmpty()) { TypeSymbol sym = w.pop(); assert isDeclarableTypeSymbol(sym); g.addVertex(sym); AlgebraicDataType type = AlgebraicDataType.makeWithFreshArgs(sym); if (sym.isUninterpretedSort()) { continue; } for (ConstructorScheme c : type.getConstructors()) { for (Type typeArg : c.getTypeArgs()) { for (TypeSymbol other : extractTypeSymbols(typeArg)) { push(other); if (isDeclarableTypeSymbol(other)) { g.addVertex(other); g.addEdge(other, sym); } } } } } return g; } private Set extractTypeSymbols(Type type) { Set syms = new HashSet<>(); type.accept( new TypeVisitor() { @Override public Void visit(TypeVar typeVar, Void in) { return null; } @Override public Void visit(AlgebraicDataType algebraicType, Void in) { syms.add(algebraicType.getSymbol()); for (Type typeArg : algebraicType.getTypeArgs()) { typeArg.accept(this, in); } return null; } @Override public Void visit(OpaqueType opaqueType, Void in) { throw new AssertionError("impossible"); } @Override public Void visit(TypeIndex typeIndex, Void in) { return null; } }, null); return syms; } private boolean isDeclarableTypeSymbol(TypeSymbol sym) { if (sym.isAlias()) { return false; } if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case SMT_TYPE: case SYM_TYPE: case BOOL_TYPE: case STRING_TYPE: case ARRAY_TYPE: case INT_TYPE: case MODEL_TYPE: case BV: case FP: case SMT_PATTERN_TYPE: case SMT_WRAPPED_VAR_TYPE: case OPAQUE_SET: return false; case CMP_TYPE: case LIST_TYPE: case OPTION_TYPE: return true; default: throw new AssertionError("impossible"); } } return true; } } private class MiniTypeInferer { private final Deque> constraints = new ArrayDeque<>(); private final Map subst = new HashMap<>(); public List> inferTypes(Term t) { constraints.clear(); subst.clear(); List> types = inferTypes1(t); unifyConstraints(); List> types2 = new ArrayList<>(); for (Pair p : types) { types2.add(new Pair<>(p.fst(), TypeChecker.simplify(p.snd().applySubst(subst)))); } return types2; } private List> inferTypes1(Term t) { List> types = new ArrayList<>(); t.accept( new TermVisitor() { @Override public Type visit(Var t, Void in) { throw new AssertionError("impossible"); } @Override public Type visit(Constructor c, Void in) { ConstructorSymbol sym = c.getSymbol(); FunctorType ft = sym.getCompileTimeType().freshen(); Type ty = ft.getRetType(); if (needsTypeAnnotation(sym)) { types.add(new Pair<>(sym, ty)); } if (!(c instanceof SolverVariable)) { Iterator it = ft.getArgTypes().iterator(); for (Term tt : c.getArgs()) { constraints.add(new Pair<>(tt.accept(this, in), it.next())); } } return ty; } @Override public Type visit(Primitive p, Void in) { return p.getType().freshen(); } @Override public Type visit(Expr expr, Void in) { throw new AssertionError("impossible"); } }, null); return types; } private void unifyConstraints() { TypeVisitor unifier = new TypeVisitor() { @Override public Void visit(TypeVar typeVar, Type other) { throw new AssertionError("impossible"); } @Override public Void visit(AlgebraicDataType algebraicType, Type other) { AlgebraicDataType otherAdt = (AlgebraicDataType) other; Iterator it = otherAdt.getTypeArgs().iterator(); for (Type arg : algebraicType.getTypeArgs()) { constraints.add(new Pair<>(arg, it.next())); } return null; } @Override public Void visit(OpaqueType opaqueType, Type other) { throw new AssertionError("impossible"); } @Override public Void visit(TypeIndex typeIndex, Type other) { assert typeIndex.equals(other); return null; } }; while (!constraints.isEmpty()) { Pair p = constraints.pop(); Type t1 = TypeChecker.simplify(TypeChecker.lookupType(p.fst(), subst)); Type t2 = TypeChecker.simplify(TypeChecker.lookupType(p.snd(), subst)); if (t1.isVar()) { handleVar((TypeVar) t1, t2); } else if (t2.isVar()) { handleVar((TypeVar) t2, t1); } else { t1.accept(unifier, t2); } } } private void handleVar(TypeVar x, Type t) { if (t.isVar()) { TypeVar y = (TypeVar) t; if (x.compareTo(y) < 0) { subst.put(x, y); } else if (x.compareTo(y) > 0) { subst.put(y, x); } return; } subst.put(x, t); } } public static boolean needsTypeAnnotation(ConstructorSymbol sym) { if (sym.getConstructorSymbolType().equals(ConstructorSymbolType.VANILLA_CONSTRUCTOR)) { return true; } FunctorType ft = sym.getCompileTimeType(); List args = ft.getArgTypes(); Type ret = sym.getCompileTimeType().getRetType(); return !Types.getTypeVars(args).containsAll(Types.getTypeVars(ret)); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtLibSolver.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.SmtLibTerm; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.Collection; public interface SmtLibSolver { void start(Program prog) throws EvaluationException; SmtResult check(Collection t, boolean getModel, int timeout) throws EvaluationException; void destroy(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtResult.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.ast.Model; public class SmtResult { public final SmtStatus status; public final Model model; public final int solverId; public final int taskId; public SmtResult(SmtStatus status, Model model, int solverId, int taskId) { this.status = status; this.model = model; this.solverId = solverId; this.taskId = taskId; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((model == null) ? 0 : model.hashCode()); result = prime * result + solverId; result = prime * result + ((status == null) ? 0 : status.hashCode()); result = prime * result + taskId; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SmtResult other = (SmtResult) obj; if (model == null) { if (other.model != null) return false; } else if (!model.equals(other.model)) return false; if (solverId != other.solverId) return false; if (status != other.status) return false; if (taskId != other.taskId) return false; return true; } @Override public String toString() { return "SmtResult [status=" + status + ", model=" + model + ", solverId=" + solverId + ", taskId=" + taskId + "]"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtStatus.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; public enum SmtStatus { SATISFIABLE, UNSATISFIABLE, UNKNOWN } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/SmtStrategy.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; public class SmtStrategy { public enum Tag { QUEUE, BEST_MATCH, NAIVE, PUSH_POP, PUSH_POP_NAIVE, PER_THREAD_QUEUE, PER_THREAD_BEST_MATCH, PER_THREAD_PUSH_POP, PER_THREAD_NAIVE, PER_THREAD_PUSH_POP_NAIVE, ; } private final Tag tag; private final Object metadata; public SmtStrategy(Tag tag, Object metadata) { this.tag = tag; this.metadata = metadata; } public Tag getTag() { return tag; } public Object getMetadata() { return metadata; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((metadata == null) ? 0 : metadata.hashCode()); result = prime * result + ((tag == null) ? 0 : tag.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SmtStrategy other = (SmtStrategy) obj; if (metadata == null) { if (other.metadata != null) return false; } else if (!metadata.equals(other.metadata)) return false; if (tag != other.tag) return false; return true; } @Override public String toString() { return "SmtStrategy [tag=" + tag + ", metadata=" + metadata + "]"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/YicesProcessFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class YicesProcessFactory implements ExternalSolverProcessFactory { private static YicesProcessFactory instance; private YicesProcessFactory() { Util.assertBinaryOnPath("yices-smt2"); } public static YicesProcessFactory get() { if (instance == null) { synchronized (YicesProcessFactory.class) { if (instance == null) { instance = new YicesProcessFactory(); } } } return instance; } @Override public Process newProcess(boolean incremental) throws IOException { List command = new ArrayList<>(); command.add("yices-smt2"); if (incremental) { command.add("--incremental"); } return new ProcessBuilder(command).redirectErrorStream(true).start(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/smt/Z3ProcessFactory.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.smt; import edu.harvard.seas.pl.formulog.util.Util; import java.io.IOException; public class Z3ProcessFactory implements ExternalSolverProcessFactory { private static Z3ProcessFactory instance; private Z3ProcessFactory() { Util.assertBinaryOnPath("z3"); } public static Z3ProcessFactory get() { if (instance == null) { synchronized (Z3ProcessFactory.class) { if (instance == null) { instance = new Z3ProcessFactory(); } } } return instance; } @Override public Process newProcess(boolean incremental) throws IOException { return new ProcessBuilder("z3", "-in", "-smt2").redirectErrorStream(true).start(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/AbstractSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; abstract class AbstractSymbol implements Symbol { private final String name; private final int arity; public AbstractSymbol(String name, int arity) { this.name = name; this.arity = arity; } @Override public int getArity() { return arity; } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/AbstractTypedSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.types.FunctorType; abstract class AbstractTypedSymbol extends AbstractSymbol implements TypedSymbol { private final FunctorType type; public AbstractTypedSymbol(String name, int arity, FunctorType type) { super(name, arity); this.type = type; } @Override public FunctorType getCompileTimeType() { return type; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/AbstractWrappedRelationSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.types.FunctorType; public abstract class AbstractWrappedRelationSymbol implements WrappedRelationSymbol { private final R baseSymbol; public AbstractWrappedRelationSymbol(R baseSymbol) { this.baseSymbol = baseSymbol; } @Override public R getBaseSymbol() { return baseSymbol; } @Override public FunctorType getCompileTimeType() { return baseSymbol.getCompileTimeType(); } @Override public int getArity() { return baseSymbol.getArity(); } @Override public boolean isIdbSymbol() { return baseSymbol.isIdbSymbol(); } @Override public boolean isBottomUp() { return baseSymbol.isEdbSymbol(); } @Override public boolean isTopDown() { return baseSymbol.isTopDown(); } @Override public boolean isDisk() { return false; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((baseSymbol == null) ? 0 : baseSymbol.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AbstractWrappedRelationSymbol other = (AbstractWrappedRelationSymbol) obj; if (baseSymbol == null) { if (other.baseSymbol != null) return false; } else if (!baseSymbol.equals(other.baseSymbol)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/BuiltInConstructorGetterSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.a; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.list; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.option; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smt; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public enum BuiltInConstructorGetterSymbol implements ConstructorSymbol { CONS_1("#cons_1", list(a), smt(a)), CONS_2("#cons_2", list(a), smt(list(a))), SOME_1("#some_1", option(a), smt(a)); private final String name; private final FunctorType type; private BuiltInConstructorGetterSymbol(String name, Type... types) { this.name = name; List argTypes = new ArrayList<>(Arrays.asList(types)); Type retType = argTypes.remove(types.length - 1); type = new FunctorType(argTypes, retType); } @Override public int getArity() { return 1; } @Override public FunctorType getCompileTimeType() { return type; } @Override public String toString() { return name; } @Override public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.SOLVER_CONSTRUCTOR_GETTER; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/BuiltInConstructorSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import static edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType.SOLVER_EXPR; import static edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType.VANILLA_CONSTRUCTOR; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.a; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.array; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.b; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bool; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bv; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.cmp; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.int_; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.list; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.option; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smt; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smtPattern; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smtWrappedVar; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.string; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.Type; public enum BuiltInConstructorSymbol implements ConstructorSymbol { // Lists NIL("nil", 0, VANILLA_CONSTRUCTOR), CONS("cons", 2, VANILLA_CONSTRUCTOR), // Options NONE("none", 0, VANILLA_CONSTRUCTOR), SOME("some", 1, VANILLA_CONSTRUCTOR), // Comparisons CMP_LT("cmp_lt", 0, VANILLA_CONSTRUCTOR), CMP_EQ("cmp_eq", 0, VANILLA_CONSTRUCTOR), CMP_GT("cmp_gt", 0, VANILLA_CONSTRUCTOR), // Constraints SMT_NOT("smt_not", 1, SOLVER_EXPR), SMT_AND("smt_and", 2, SOLVER_EXPR), SMT_OR("smt_or", 2, SOLVER_EXPR), SMT_IMP("smt_imp", 2, SOLVER_EXPR), SMT_ITE("smt_ite", 3, SOLVER_EXPR), SMT_EXISTS("smt_exists", 3, SOLVER_EXPR), SMT_FORALL("smt_forall", 3, SOLVER_EXPR), // Bit vectors BV_NEG("bv_neg", 1, SOLVER_EXPR), BV_ADD("bv_add", 2, SOLVER_EXPR), BV_SUB("bv_sub", 2, SOLVER_EXPR), BV_MUL("bv_mul", 2, SOLVER_EXPR), BV_SDIV("bv_sdiv", 2, SOLVER_EXPR), BV_SREM("bv_srem", 2, SOLVER_EXPR), BV_UDIV("bv_udiv", 2, SOLVER_EXPR), BV_UREM("bv_urem", 2, SOLVER_EXPR), BV_AND("bv_and", 2, SOLVER_EXPR), BV_OR("bv_or", 2, SOLVER_EXPR), BV_XOR("bv_xor", 2, SOLVER_EXPR), BV_SHL("bv_shl", 2, SOLVER_EXPR), BV_ASHR("bv_ashr", 2, SOLVER_EXPR), BV_LSHR("bv_lshr", 2, SOLVER_EXPR), // Floating point FP_NEG("fp_neg", 1, SOLVER_EXPR), FP_ADD("fp_add", 2, SOLVER_EXPR), FP_SUB("fp_sub", 2, SOLVER_EXPR), FP_MUL("fp_mul", 2, SOLVER_EXPR), FP_DIV("fp_div", 2, SOLVER_EXPR), FP_REM("fp_rem", 2, SOLVER_EXPR), // Arrays ARRAY_STORE("array_store", 3, SOLVER_EXPR), ARRAY_CONST("array_const", 1, SOLVER_EXPR), // Strings STR_CONCAT("str_concat", 2, SOLVER_EXPR), STR_LEN("str_len", 1, SOLVER_EXPR), STR_SUBSTR("str_substr", 3, SOLVER_EXPR), STR_INDEXOF("str_indexof", 3, SOLVER_EXPR), STR_AT("str_at", 2, SOLVER_EXPR), STR_CONTAINS("str_contains", 2, SOLVER_EXPR), STR_PREFIXOF("str_prefixof", 2, SOLVER_EXPR), STR_SUFFIXOF("str_suffixof", 2, SOLVER_EXPR), STR_REPLACE("str_replace", 3, SOLVER_EXPR), // Ints INT_CONST("int_const", 1, SOLVER_EXPR), INT_BIG_CONST("int_big_const", 1, SOLVER_EXPR), INT_NEG("int_neg", 1, SOLVER_EXPR), INT_SUB("int_sub", 2, SOLVER_EXPR), INT_ADD("int_add", 2, SOLVER_EXPR), INT_MUL("int_mul", 2, SOLVER_EXPR), INT_DIV("int_div", 2, SOLVER_EXPR), INT_MOD("int_mod", 2, SOLVER_EXPR), INT_ABS("int_abs", 1, SOLVER_EXPR), INT_LE("int_le", 2, SOLVER_EXPR), INT_LT("int_lt", 2, SOLVER_EXPR), INT_GE("int_ge", 2, SOLVER_EXPR), INT_GT("int_gt", 2, SOLVER_EXPR), // Stuff for type checking formulas ENTER_FORMULA("enter_formula", 1, VANILLA_CONSTRUCTOR), EXIT_FORMULA("exit_formula", 1, VANILLA_CONSTRUCTOR), ; private final String name; private final int arity; private final ConstructorSymbolType st; private BuiltInConstructorSymbol(String name, int arity, ConstructorSymbolType st) { this.name = name; this.arity = arity; this.st = st; } @Override public int getArity() { return arity; } private FunctorType makeType(Type... types) { assert types.length == arity + 1; return new FunctorType(types); } @Override public FunctorType getCompileTimeType() { switch (this) { case CMP_EQ: case CMP_GT: case CMP_LT: return makeType(cmp); case NIL: return makeType(list(a)); case CONS: return makeType(a, list(a), list(a)); case SMT_AND: case SMT_OR: case SMT_IMP: return makeType(smt(bool), smt(bool), smt(bool)); case SMT_ITE: return makeType(smt(bool), smt(a), smt(a), smt(a)); case SMT_NOT: return makeType(smt(bool), smt(bool)); case SMT_EXISTS: case SMT_FORALL: return makeType(list(smtWrappedVar), smt(bool), list(list(smtPattern)), smt(bool)); case NONE: return makeType(option(a)); case SOME: return makeType(a, option(a)); case BV_ADD: case BV_AND: case BV_MUL: case BV_OR: case BV_SDIV: case BV_SREM: case BV_UDIV: case BV_UREM: case BV_SUB: case BV_XOR: case BV_SHL: case BV_ASHR: case BV_LSHR: return makeType(smt(bv(a)), smt(bv(a)), smt(bv(a))); case BV_NEG: return makeType(smt(bv(a)), smt(bv(a))); case FP_ADD: case FP_DIV: case FP_REM: case FP_SUB: case FP_MUL: return makeType(smt(fp(a, b)), smt(fp(a, b)), smt(fp(a, b))); case FP_NEG: return makeType(smt(fp(a, b)), smt(fp(a, b))); case ARRAY_STORE: return makeType(smt(array(a, b)), smt(a), smt(b), smt(array(a, b))); case ARRAY_CONST: return makeType(smt(b), smt(array(a, b))); case STR_AT: return makeType(smt(string), smt(int_), smt(string)); case STR_CONCAT: return makeType(smt(string), smt(string), smt(string)); case STR_CONTAINS: return makeType(smt(string), smt(string), smt(bool)); case STR_INDEXOF: return makeType(smt(string), smt(string), smt(int_), smt(int_)); case STR_LEN: return makeType(smt(string), smt(int_)); case STR_PREFIXOF: return makeType(smt(string), smt(string), smt(bool)); case STR_REPLACE: return makeType(smt(string), smt(string), smt(string), smt(string)); case STR_SUBSTR: return makeType(smt(string), smt(int_), smt(int_), smt(string)); case STR_SUFFIXOF: return makeType(smt(string), smt(string), smt(bool)); case INT_ABS: case INT_NEG: return makeType(smt(int_), smt(int_)); case INT_BIG_CONST: return makeType(bv(64), smt(int_)); case INT_CONST: return makeType(bv(32), smt(int_)); case INT_GE: case INT_GT: case INT_LE: case INT_LT: return makeType(smt(int_), smt(int_), smt(bool)); case INT_ADD: case INT_MUL: case INT_MOD: case INT_SUB: case INT_DIV: return makeType(smt(int_), smt(int_), smt(int_)); case ENTER_FORMULA: return makeType(smt(a), smt(a)); case EXIT_FORMULA: return makeType(a, a); } throw new AssertionError("impossible"); } @Override public ConstructorSymbolType getConstructorSymbolType() { return st; } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/BuiltInConstructorTesterSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.a; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bool; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.cmp; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.list; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.option; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smt; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public enum BuiltInConstructorTesterSymbol implements ConstructorSymbol { IS_CMP_LT("#is_cmp_lt", cmp, smt(bool)), IS_CMP_EQ("#is_cmp_eq", cmp, smt(bool)), IS_CMP_GT("#is_cmp_gt", cmp, smt(bool)), IS_NIL("#is_nil", list(a), smt(bool)), IS_CONS("#is_cons", list(a), smt(bool)), IS_NONE("#is_none", option(a), smt(bool)), IS_SOME("#is_some", option(a), smt(bool)), ; private final FunctorType type; private final String name; private BuiltInConstructorTesterSymbol(String name, Type... types) { this.name = name; List argTypes = new ArrayList<>(Arrays.asList(types)); Type retType = argTypes.remove(types.length - 1); type = new FunctorType(argTypes, retType); } @Override public int getArity() { return 1; } @Override public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.SOLVER_CONSTRUCTOR_TESTER; } @Override public FunctorType getCompileTimeType() { return type; } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/BuiltInFunctionSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.a; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bool; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.cmp; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp32; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp64; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.i32; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.i64; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.list; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.model; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.opaqueSet; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.option; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.pair; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smt; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.string; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.sym; import edu.harvard.seas.pl.formulog.types.FunctorType; public enum BuiltInFunctionSymbol implements FunctionSymbol { // i32 operations I32_ADD("i32_add", 2), I32_SUB("i32_sub", 2), I32_MUL("i32_mul", 2), I32_SDIV("i32_sdiv", 2), I32_SREM("i32_srem", 2), I32_UDIV("i32_udiv", 2), I32_UREM("i32_urem", 2), I32_NEG("i32_neg", 1), I32_LT("i32_lt", 2), I32_LE("i32_le", 2), I32_GT("i32_gt", 2), I32_GE("i32_ge", 2), I32_AND("i32_and", 2), I32_OR("i32_or", 2), I32_XOR("i32_xor", 2), I32_SCMP("i32_scmp", 2), I32_UCMP("i32_ucmp", 2), I32_SHL("i32_shl", 2), I32_ASHR("i32_ashr", 2), I32_LSHR("i32_lshr", 2), // i64 operations I64_ADD("i64_add", 2), I64_SUB("i64_sub", 2), I64_MUL("i64_mul", 2), I64_SDIV("i64_sdiv", 2), I64_SREM("i64_srem", 2), I64_UDIV("i64_udiv", 2), I64_UREM("i64_urem", 2), I64_NEG("i64_neg", 1), I64_LT("i64_lt", 2), I64_LE("i64_le", 2), I64_GT("i64_gt", 2), I64_GE("i64_ge", 2), I64_AND("i64_and", 2), I64_OR("i64_or", 2), I64_XOR("i64_xor", 2), I64_SCMP("i64_scmp", 2), I64_UCMP("i64_ucmp", 2), I64_SHL("i64_shl", 2), I64_ASHR("i64_ashr", 2), I64_LSHR("i64_lshr", 2), // fp32 operations FP32_ADD("fp32_add", 2), FP32_SUB("fp32_sub", 2), FP32_MUL("fp32_mul", 2), FP32_DIV("fp32_div", 2), FP32_REM("fp32_rem", 2), FP32_NEG("fp32_neg", 1), FP32_LT("fp32_lt", 2), FP32_LE("fp32_le", 2), FP32_GT("fp32_gt", 2), FP32_GE("fp32_ge", 2), FP32_EQ("fp32_eq", 2), // fp64 operations FP64_ADD("fp64_add", 2), FP64_SUB("fp64_sub", 2), FP64_MUL("fp64_mul", 2), FP64_DIV("fp64_div", 2), FP64_REM("fp64_rem", 2), FP64_NEG("fp64_neg", 1), FP64_LT("fp64_lt", 2), FP64_LE("fp64_le", 2), FP64_GT("fp64_gt", 2), FP64_GE("fp64_ge", 2), FP64_EQ("fp64_eq", 2), // Boolean operations BEQ("beq", 2), BNEQ("bneq", 2), BNOT("bnot", 1), // String operations STRING_CMP("string_cmp", 2), STRING_CONCAT("string_concat", 2), STRING_MATCHES("string_matches", 2), STRING_STARTS_WITH("string_starts_with", 2), TO_STRING("to_string", 1), STRING_TO_LIST("string_to_list", 1), LIST_TO_STRING("list_to_string", 1), CHAR_AT("char_at", 2), SUBSTRING("substring", 3), STRING_LENGTH("string_length", 1), // Constraint solving IS_SAT("is_sat", 1), IS_VALID("is_valid", 1), IS_SAT_OPT("is_sat_opt", 2), GET_MODEL("get_model", 2), QUERY_MODEL("query_model", 2), IS_SET_SAT("is_set_sat", 2), // Opaque datatypes OPAQUE_SET_EMPTY("opaque_set_empty", 0), OPAQUE_SET_PLUS("opaque_set_plus", 2), OPAQUE_SET_MINUS("opaque_set_minus", 2), OPAQUE_SET_UNION("opaque_set_union", 2), OPAQUE_SET_DIFF("opaque_set_diff", 2), OPAQUE_SET_CHOOSE("opaque_set_choose", 1), OPAQUE_SET_SIZE("opaque_set_size", 1), OPAQUE_SET_MEMBER("opaque_set_member", 2), OPAQUE_SET_SINGLETON("opaque_set_singleton", 1), OPAQUE_SET_SUBSET("opaque_set_subset", 2), OPAQUE_SET_FROM_LIST("opaque_set_from_list", 1), // Primitive conversion i32ToI64("i32_to_i64", 1), i32ToFp32("i32_to_fp32", 1), i32ToFp64("i32_to_fp64", 1), i64ToI32("i64_to_i32", 1), i64ToFp32("i64_to_fp32", 1), i64ToFp64("i64_to_fp64", 1), fp32ToI32("fp32_to_i32", 1), fp32ToI64("fp32_to_i64", 1), fp32ToFp64("fp32_to_fp64", 1), fp64ToI32("fp64_to_i32", 1), fp64ToI64("fp64_to_i64", 1), fp64ToFp32("fp64_to_fp32", 1), stringToI32("string_to_i32", 1), stringToI64("string_to_i64", 1), // Debugging PRINT("print", 1), ; private final String name; private final int arity; BuiltInFunctionSymbol(String name, int arity) { this.name = name; this.arity = arity; } @Override public int getArity() { return arity; } @Override public FunctorType getCompileTimeType() { switch (this) { case BEQ: case BNEQ: return new FunctorType(a, a, bool); case BNOT: return new FunctorType(bool, bool); case FP32_NEG: return new FunctorType(fp32, fp32); case FP32_ADD: case FP32_DIV: case FP32_MUL: case FP32_REM: case FP32_SUB: return new FunctorType(fp32, fp32, fp32); case FP32_EQ: case FP32_GE: case FP32_GT: case FP32_LE: case FP32_LT: return new FunctorType(fp32, fp32, bool); case FP64_NEG: return new FunctorType(fp64, fp64); case FP64_ADD: case FP64_DIV: case FP64_MUL: case FP64_REM: case FP64_SUB: return new FunctorType(fp64, fp64, fp64); case FP64_EQ: case FP64_GE: case FP64_GT: case FP64_LE: case FP64_LT: return new FunctorType(fp64, fp64, bool); case GET_MODEL: return new FunctorType(list(smt(bool)), option(i32), option(model)); case I32_NEG: return new FunctorType(i32, i32); case I32_ADD: case I32_AND: case I32_SDIV: case I32_MUL: case I32_OR: case I32_SREM: case I32_UDIV: case I32_UREM: case I32_SUB: case I32_XOR: case I32_SHL: case I32_ASHR: case I32_LSHR: return new FunctorType(i32, i32, i32); case I32_GE: case I32_GT: case I32_LE: case I32_LT: return new FunctorType(i32, i32, bool); case I32_SCMP: case I32_UCMP: return new FunctorType(i32, i32, cmp); case I64_NEG: return new FunctorType(i64, i64); case I64_ADD: case I64_AND: case I64_SDIV: case I64_MUL: case I64_OR: case I64_SREM: case I64_UDIV: case I64_UREM: case I64_SUB: case I64_XOR: case I64_SHL: case I64_ASHR: case I64_LSHR: return new FunctorType(i64, i64, i64); case I64_GE: case I64_GT: case I64_LE: case I64_LT: return new FunctorType(i64, i64, bool); case I64_SCMP: case I64_UCMP: return new FunctorType(i64, i64, cmp); case IS_SAT: case IS_VALID: return new FunctorType(smt(bool), bool); case IS_SET_SAT: return new FunctorType(opaqueSet(smt(bool)), option(i32), option(bool)); case IS_SAT_OPT: return new FunctorType(list(smt(bool)), option(i32), option(bool)); case PRINT: return new FunctorType(a, bool); case QUERY_MODEL: return new FunctorType(sym(a), model, option(a)); case STRING_CONCAT: return new FunctorType(string, string, string); case STRING_CMP: return new FunctorType(string, string, cmp); case STRING_MATCHES: return new FunctorType(string, string, bool); case TO_STRING: return new FunctorType(a, string); case STRING_STARTS_WITH: return new FunctorType(string, string, bool); case STRING_TO_LIST: return new FunctorType(string, list(i32)); case LIST_TO_STRING: return new FunctorType(list(i32), string); case CHAR_AT: return new FunctorType(string, i32, option(i32)); case SUBSTRING: return new FunctorType(string, i32, i32, option(string)); case STRING_LENGTH: return new FunctorType(string, i32); case fp32ToFp64: return new FunctorType(fp32, fp64); case fp32ToI32: return new FunctorType(fp32, i32); case fp32ToI64: return new FunctorType(fp32, i64); case fp64ToFp32: return new FunctorType(fp64, fp32); case fp64ToI32: return new FunctorType(fp64, i32); case fp64ToI64: return new FunctorType(fp64, i64); case i32ToFp32: return new FunctorType(i32, fp32); case i32ToFp64: return new FunctorType(i32, fp64); case i32ToI64: return new FunctorType(i32, i64); case i64ToFp32: return new FunctorType(i64, fp32); case i64ToFp64: return new FunctorType(i64, fp64); case i64ToI32: return new FunctorType(i64, i32); case stringToI32: return new FunctorType(string, option(i32)); case stringToI64: return new FunctorType(string, option(i64)); case OPAQUE_SET_CHOOSE: return new FunctorType(opaqueSet(a), option(pair(a, opaqueSet(a)))); case OPAQUE_SET_DIFF: return new FunctorType(opaqueSet(a), opaqueSet(a), opaqueSet(a)); case OPAQUE_SET_EMPTY: return new FunctorType(opaqueSet(a)); case OPAQUE_SET_PLUS: return new FunctorType(a, opaqueSet(a), opaqueSet(a)); case OPAQUE_SET_MINUS: return new FunctorType(a, opaqueSet(a), opaqueSet(a)); case OPAQUE_SET_UNION: return new FunctorType(opaqueSet(a), opaqueSet(a), opaqueSet(a)); case OPAQUE_SET_SIZE: return new FunctorType(opaqueSet(a), i32); case OPAQUE_SET_MEMBER: return new FunctorType(a, opaqueSet(a), bool); case OPAQUE_SET_SINGLETON: return new FunctorType(a, opaqueSet(a)); case OPAQUE_SET_SUBSET: return new FunctorType(opaqueSet(a), opaqueSet(a), bool); case OPAQUE_SET_FROM_LIST: return new FunctorType(list(a), opaqueSet(a)); } throw new AssertionError("impossible"); } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/BuiltInTypeSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public enum BuiltInTypeSymbol implements TypeSymbol { BOOL_TYPE("bool", 0), LIST_TYPE("list", 1), OPTION_TYPE("option", 1), CMP_TYPE("cmp", 0), STRING_TYPE("string", 0), SMT_TYPE("smt", 1), SYM_TYPE("sym", 1), ARRAY_TYPE("array", 2), MODEL_TYPE("model", 0), INT_TYPE("int", 0), BV("bv", 1), FP("fp", 2), OPAQUE_SET("opaque_set", 1), SMT_PATTERN_TYPE("smt_pattern", 0), SMT_WRAPPED_VAR_TYPE("smt_wrapped_var", 0), ; private final String name; private final int arity; private BuiltInTypeSymbol(String name, int arity) { this.name = name; this.arity = arity; } @Override public int getArity() { return arity; } @Override public TypeSymbolType getTypeSymbolType() { return TypeSymbolType.NORMAL_TYPE; } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/ConstructorSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface ConstructorSymbol extends TypedSymbol { ConstructorSymbolType getConstructorSymbolType(); default boolean isSolverConstructorSymbol() { switch (getConstructorSymbolType()) { case SOLVER_CONSTRUCTOR_GETTER: case SOLVER_CONSTRUCTOR_TESTER: case SOLVER_EXPR: case SOLVER_UNINTERPRETED_FUNCTION: case INDEX_CONSTRUCTOR: case VANILLA_CONSTRUCTOR: return false; } throw new AssertionError("impossible"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/ConstructorSymbolImpl.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.types.FunctorType; class ConstructorSymbolImpl extends AbstractTypedSymbol implements ConstructorSymbol { private final ConstructorSymbolType symType; public ConstructorSymbolImpl( String name, int arity, ConstructorSymbolType symType, FunctorType type) { super(name, arity, type); this.symType = symType; } @Override public ConstructorSymbolType getConstructorSymbolType() { return symType; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/ConstructorSymbolType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public enum ConstructorSymbolType { SOLVER_UNINTERPRETED_FUNCTION, VANILLA_CONSTRUCTOR, SOLVER_EXPR, SOLVER_CONSTRUCTOR_GETTER, SOLVER_CONSTRUCTOR_TESTER, INDEX_CONSTRUCTOR, ; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/FunctionSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface FunctionSymbol extends TypedSymbol {} ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/GlobalSymbolManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedSymbol; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public final class GlobalSymbolManager { private GlobalSymbolManager() { throw new AssertionError("impossible"); } private static boolean initialized = false; private static final Map memo = new ConcurrentHashMap<>(); private static final Set typeSymbols = Util.concurrentSet(); public static boolean hasName(String name) { checkInitialized(); return memo.containsKey(name); } public static Symbol lookup(String name) { return lookup(name, Collections.emptyList()); } public static Symbol lookup(String name, Param... params) { return lookup(name, Arrays.asList(params)); } public static Symbol lookup(String name, List params) { checkInitialized(); if (!hasName(name)) { throw new IllegalArgumentException("Unrecognized name: " + name); } Symbol sym = memo.get(name); assert sym != null; if (sym instanceof ParameterizedSymbol) { sym = ((ParameterizedSymbol) sym).copyWithNewArgs(params); } else if (!params.isEmpty()) { throw new IllegalArgumentException( "Cannot supply parameters to non-parameterized symbol: " + sym); } return sym; } public static ParameterizedConstructorSymbol getParameterizedSymbol( BuiltInConstructorSymbolBase base, List params) { return getParameterizedSymbol(base).copyWithNewArgs(params); } public static ParameterizedConstructorSymbol getParameterizedSymbol( BuiltInConstructorSymbolBase base) { initialize(); return getParameterizedSymbolInternal(base); } private static ParameterizedConstructorSymbol getParameterizedSymbolInternal( BuiltInConstructorSymbolBase base) { List params = Param.wildCards(base.getNumParams()); return ParameterizedConstructorSymbol.mk(base, params); } private static void checkInitialized() { if (!initialized) { initialize(); } } private static synchronized void initialize() { if (initialized) { return; } register(BuiltInTypeSymbol.values()); register(BuiltInConstructorSymbol.values()); register(BuiltInConstructorTesterSymbol.values()); register(BuiltInConstructorGetterSymbol.values()); register(BuiltInConstructorSymbolBase.values()); register(BuiltInFunctionSymbol.values()); initialized = true; } private static void register(Symbol[] symbols) { for (Symbol sym : symbols) { register(sym); } } private static void register(Symbol sym) { if (sym instanceof TypeSymbol) { typeSymbols.add((TypeSymbol) sym); } Symbol other = memo.putIfAbsent(sym.toString(), sym); assert other == null; } private static void register(BuiltInConstructorSymbolBase[] bases) { for (BuiltInConstructorSymbolBase base : bases) { ParameterizedConstructorSymbol sym = getParameterizedSymbolInternal(base); Symbol other = memo.putIfAbsent(base.toString(), sym); assert other == null; } } private static TypeSymbol createTypeSymbol(String name, int arity, TypeSymbolType symType) { initialize(); TypeSymbol sym = new TypeSymbolImpl(name, arity, symType); register(sym); return sym; } private static ConstructorSymbol createConstructorSymbol( String name, int arity, ConstructorSymbolType symType, FunctorType type) { initialize(); ConstructorSymbol sym = new ConstructorSymbolImpl(name, arity, symType, type); register(sym); return sym; } public static Set getTypeSymbols() { initialize(); return Collections.unmodifiableSet(typeSymbols); } private static final Map tupleSymbolMemo = new ConcurrentHashMap<>(); private static final Map tupleTypeSymbolMemo = new ConcurrentHashMap<>(); public static TupleSymbol lookupTupleSymbol(int arity) { instantiateTuple(arity); return tupleSymbolMemo.get(arity); } public static TypeSymbol lookupTupleTypeSymbol(int arity) { instantiateTuple(arity); return tupleTypeSymbolMemo.get(arity); } private static void instantiateTuple(int arity) { TupleSymbol tupSym = tupleSymbolMemo.get(arity); if (tupSym != null) { return; } TypeSymbol typeSym = createTypeSymbol("tuple_type$" + arity, arity, TypeSymbolType.NORMAL_TYPE); List typeArgs = new ArrayList<>(); List typeVars = new ArrayList<>(); for (int i = 0; i < arity; ++i) { TypeVar x = TypeVar.fresh(); typeArgs.add(x); typeVars.add(x); } AlgebraicDataType type = AlgebraicDataType.make(typeSym, typeArgs); List getters = new ArrayList<>(); int i = 0; for (Type ty : typeArgs) { String getter = "#_tuple" + arity + "_" + (i + 1); FunctorType ft = new FunctorType(type, ty); getters.add( createConstructorSymbol( getter, arity, ConstructorSymbolType.SOLVER_CONSTRUCTOR_GETTER, ft)); ++i; } FunctorType ctorTy = new FunctorType(typeArgs, type); tupSym = new TupleSymbol(arity, ctorTy); ConstructorScheme cs = new ConstructorScheme(tupSym, typeArgs, getters); AlgebraicDataType.setConstructors(typeSym, typeVars, Collections.singleton(cs)); tupleSymbolMemo.put(arity, tupSym); tupleTypeSymbolMemo.put(arity, typeSym); } public static class TupleSymbol implements ConstructorSymbol { private final int arity; private final FunctorType type; private TupleSymbol(int arity, FunctorType type) { this.arity = arity; this.type = type; } @Override public FunctorType getCompileTimeType() { return type; } @Override public int getArity() { return arity; } @Override public String toString() { return "tuple$" + arity; } @Override public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.VANILLA_CONSTRUCTOR; } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/MutableRelationSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface MutableRelationSymbol extends RelationSymbol { void setTopDown(); void setBottomUp(); void setDisk(); void setEdb(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/PredicateFunctionSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.db.BindingTypeArrayWrapper; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class PredicateFunctionSymbol implements FunctionSymbol { private static final Map> memo = new HashMap<>(); private static final Map placeholders = new HashMap<>(); public static synchronized PredicateFunctionSymbol create( RelationSymbol predSym, BindingType[] bindings, SymbolManager sm) { Map m = Util.lookupOrCreate(memo, predSym, () -> new HashMap<>()); BindingTypeArrayWrapper key = new BindingTypeArrayWrapper(bindings); PredicateFunctionSymbol funcSym = m.get(key); if (funcSym == null) { funcSym = createNew(predSym, bindings, sm); PredicateFunctionSymbol f2 = m.put(key, funcSym); assert f2 == null; } return funcSym; } public static synchronized PredicateFunctionSymbol createPlaceholder( RelationSymbol predSym, SymbolManager sm) { PredicateFunctionSymbol sym = Util.lookupOrCreate( placeholders, predSym, () -> new PredicateFunctionSymbol(predSym, null, predSym.getCompileTimeType())); sm.registerSymbol(sym); return sym; } private static PredicateFunctionSymbol createNew( RelationSymbol predSym, BindingType[] bindings, SymbolManager sm) { assert predSym.getArity() == bindings.length; List argTypes = new ArrayList<>(); List retTypes = new ArrayList<>(); FunctorType type = predSym.getCompileTimeType(); int i = 0; for (Type ty : type.getArgTypes()) { if (bindings[i].isBound()) { argTypes.add(ty); } else if (bindings[i].isFree()) { retTypes.add(ty); } i++; } Type retType; if (retTypes.isEmpty()) { retType = BuiltInTypes.bool; } else if (retTypes.size() == 1) { retType = BuiltInTypes.list(retTypes.get(0)); } else { TypeSymbol tupTypeSym = GlobalSymbolManager.lookupTupleTypeSymbol(retTypes.size()); retType = BuiltInTypes.list(AlgebraicDataType.make(tupTypeSym, retTypes)); } type = new FunctorType(argTypes, retType); PredicateFunctionSymbol funcSym = new PredicateFunctionSymbol(predSym, bindings, type); sm.registerSymbol(funcSym); return funcSym; } private final RelationSymbol predSymbol; private final BindingType[] bindings; private final FunctorType type; private PredicateFunctionSymbol( RelationSymbol predSymbol, BindingType[] bindings, FunctorType type) { this.predSymbol = predSymbol; this.bindings = bindings; this.type = type; } @Override public int getArity() { return type.getArgTypes().size(); } @Override public FunctorType getCompileTimeType() { return type; } public RelationSymbol getPredicateSymbol() { return predSymbol; } public BindingType[] getBindings() { return bindings; } @Override public String toString() { String s = predSymbol.toString(); if (bindings != null) { if (bindings.length > 0) { s += "<"; for (int i = 0; i < bindings.length; ++i) { s += bindings[i]; if (i < bindings.length - 1) { s += ","; } } s += ">"; } } else { s += "?"; } return s + "?"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/RecordSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import java.util.List; public interface RecordSymbol extends ConstructorSymbol { List getLabels(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/RelationSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface RelationSymbol extends TypedSymbol { boolean isIdbSymbol(); default boolean isEdbSymbol() { return !isIdbSymbol(); } boolean isDisk(); boolean isBottomUp(); boolean isTopDown(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/Symbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface Symbol { int getArity(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/SymbolComparator.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import java.util.Comparator; public enum SymbolComparator implements Comparator { INSTANCE; @Override public int compare(Symbol o1, Symbol o2) { return o1.toString().compareTo(o2.toString()); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/SymbolManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.symbols.parameterized.BuiltInConstructorSymbolBase; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.SymbolBase; import edu.harvard.seas.pl.formulog.types.BuiltInTypes; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.util.TodoException; import edu.harvard.seas.pl.formulog.util.Util; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class SymbolManager { private final Map memo = new ConcurrentHashMap<>(); private final Set typeSymbols = Util.concurrentSet(); public TypeSymbol createTypeSymbol(String name, int arity, TypeSymbolType st) { checkNotUsed(name); TypeSymbol sym = new TypeSymbolImpl(name, arity, st); registerSymbol(sym); return sym; } public ConstructorSymbol createConstructorSymbol( String name, int arity, ConstructorSymbolType st, FunctorType type) { checkNotUsed(name); ConstructorSymbol sym = new ConstructorSymbolImpl(name, arity, st, type); registerSymbol(sym); return sym; } public RecordSymbol createRecordSymbol( String name, int arity, FunctorType type, List labels) { checkNotUsed(name); RecordSymbol sym = new RecordSymbolImpl(name, arity, type, labels); registerSymbol(sym); return sym; } public FunctionSymbol createFunctionSymbol(String name, int arity, FunctorType type) { checkNotUsed(name); FunctionSymbol sym = new FunctionSymbolImpl(name, arity, type); registerSymbol(sym); return sym; } public MutableRelationSymbol createRelationSymbol(String name, int arity, FunctorType type) { checkNotUsed(name); MutableRelationSymbol sym = new RelationSymbolImpl(name, arity, type); registerSymbol(sym); return sym; } public void checkNotUsed(String name) { if (hasName(name)) { throw new IllegalArgumentException( "Cannot create symbol " + name + "; a symbol already exists with that name."); } } public boolean hasName(String name) { return memo.containsKey(name) || GlobalSymbolManager.hasName(name); } public boolean hasConstructorSymbolWithName(String name) { if (!hasName(name)) { return false; } return lookupSymbol(name) instanceof ConstructorSymbol; } public Symbol lookupSymbol(String name) { return lookupSymbol(name, Collections.emptyList()); } public ParameterizedSymbol getParameterizedSymbol(SymbolBase base) { if (base instanceof BuiltInConstructorSymbolBase) { return GlobalSymbolManager.getParameterizedSymbol((BuiltInConstructorSymbolBase) base); } throw new TodoException(); } public Symbol lookupSymbol(String name, List params) { if (GlobalSymbolManager.hasName(name)) { return GlobalSymbolManager.lookup(name, params); } if (!hasName(name)) { throw new IllegalArgumentException("No symbol exists with name " + name + "."); } Symbol sym = memo.get(name); assert sym != null; if (sym instanceof ParameterizedSymbol) { return ((ParameterizedSymbol) sym).copyWithNewArgs(params); } else if (!params.isEmpty()) { throw new IllegalArgumentException( "Cannot supply parameters to non-parameterized symbol: " + sym); } return sym; } public PredicateFunctionSymbol createPredicateFunctionSymbol( RelationSymbol sym, BindingType[] bindings) { return PredicateFunctionSymbol.create(sym, bindings, this); } public PredicateFunctionSymbol createPredicateFunctionSymbolPlaceholder(RelationSymbol sym) { return PredicateFunctionSymbol.createPlaceholder(sym, this); } public ConstructorSymbol lookupIndexConstructorSymbol(int index) { String name = "index$" + index; ConstructorSymbol sym = (ConstructorSymbol) memo.get(name); if (sym == null) { sym = createConstructorSymbol( name, 1, ConstructorSymbolType.INDEX_CONSTRUCTOR, new FunctorType(BuiltInTypes.i32, TypeIndex.make(index))); registerSymbol(sym); } return sym; } private static class FunctionSymbolImpl extends AbstractTypedSymbol implements FunctionSymbol { public FunctionSymbolImpl(String name, int arity, FunctorType type) { super(name, arity, type); } } private static class RecordSymbolImpl extends AbstractTypedSymbol implements RecordSymbol { private final List labels; public RecordSymbolImpl(String name, int arity, FunctorType type, List labels) { super(name, arity, type); this.labels = labels; } @Override public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.VANILLA_CONSTRUCTOR; } @Override public List getLabels() { return labels; } } private static class RelationSymbolImpl extends AbstractTypedSymbol implements MutableRelationSymbol { private enum Mode { vanillaIdb, topDownIdb, bottomUpIdb, edb } private boolean disk; private Mode mode = Mode.vanillaIdb; public RelationSymbolImpl(String name, int arity, FunctorType type) { super(name, arity, type); } @Override public boolean isIdbSymbol() { return !isEdbSymbol(); } @Override public boolean isEdbSymbol() { return mode == Mode.edb; } @Override public boolean isDisk() { return disk; } @Override public synchronized boolean isBottomUp() { return mode == Mode.bottomUpIdb; } @Override public synchronized boolean isTopDown() { return mode == Mode.topDownIdb; } @Override public synchronized void setTopDown() { if (mode == Mode.bottomUpIdb) { throw new IllegalStateException("Relation cannot be both top-down and bottom-up"); } mode = Mode.topDownIdb; } @Override public synchronized void setBottomUp() { if (mode == Mode.topDownIdb) { throw new IllegalStateException("Relation cannot be both top-down and bottom-up"); } mode = Mode.bottomUpIdb; } @Override public synchronized void setDisk() { disk = true; } @Override public void setEdb() { if (mode != Mode.edb && mode != Mode.vanillaIdb) { throw new IllegalStateException( "Relation cannot be an EDB relation with other qualifier: " + mode); } mode = Mode.edb; } } public void registerSymbol(Symbol sym) { if (sym instanceof TypeSymbol) { typeSymbols.add((TypeSymbol) sym); } Symbol sym2 = memo.putIfAbsent(sym.toString(), sym); if (sym2 != null && !sym2.equals(sym)) { throw new IllegalArgumentException( "Cannot register symbol " + sym + "; a different symbol is already registered with that name."); } } public Set getTypeSymbols() { Set syms = new HashSet<>(typeSymbols); syms.addAll(GlobalSymbolManager.getTypeSymbols()); return Collections.unmodifiableSet(syms); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/TypeSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface TypeSymbol extends Symbol { TypeSymbolType getTypeSymbolType(); default boolean isNormalType() { return getTypeSymbolType().equals(TypeSymbolType.NORMAL_TYPE); } default boolean isAlias() { return getTypeSymbolType().equals(TypeSymbolType.TYPE_ALIAS); } default boolean isUninterpretedSort() { return getTypeSymbolType().equals(TypeSymbolType.UNINTERPRETED_SORT); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/TypeSymbolImpl.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; class TypeSymbolImpl extends AbstractSymbol implements TypeSymbol { private final TypeSymbolType symType; public TypeSymbolImpl(String name, int arity, TypeSymbolType symType) { super(name, arity); this.symType = symType; } @Override public TypeSymbolType getTypeSymbolType() { return symType; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/TypeSymbolType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public enum TypeSymbolType { NORMAL_TYPE, TYPE_ALIAS, UNINTERPRETED_SORT, ; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/TypedSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; import edu.harvard.seas.pl.formulog.types.FunctorType; public interface TypedSymbol extends Symbol { FunctorType getCompileTimeType(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/WrappedRelationSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols; public interface WrappedRelationSymbol extends RelationSymbol { R getBaseSymbol(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/AbstractParameterizedSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import edu.harvard.seas.pl.formulog.types.Types; import java.util.ArrayList; import java.util.List; public abstract class AbstractParameterizedSymbol implements ParameterizedSymbol { private final B base; private final List args; public AbstractParameterizedSymbol(B base, List args) { if (base.getNumParams() != args.size()) { throw new IllegalArgumentException( "Wrong number of parameters for symbol " + base + ", which has parameter arity " + base.getNumParams() + " but received parameters " + args); } this.base = base; this.args = new ArrayList<>(); List kinds = base.getParamKinds(); for (int i = 0; i < kinds.size(); ++i) { Param param = new Param(args.get(i).getType(), kinds.get(i)); this.args.add(param); } } @Override public int getArity() { return base.getArity(); } @Override public String toString() { String s = base.toString(); s += "<"; for (int i = 0; i < args.size(); ++i) { s += args.get(i); if (i < args.size() - 1) { s += ", "; } } return s + ">"; } @Override public B getBase() { return base; } @Override public List getArgs() { return args; } @Override public boolean isGround() { for (Param arg : args) { if (Types.containsTypeVarOrOpaqueType(arg.getType())) { return false; } } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((args == null) ? 0 : args.hashCode()); result = prime * result + ((base == null) ? 0 : base.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("rawtypes") AbstractParameterizedSymbol other = (AbstractParameterizedSymbol) obj; if (args == null) { if (other.args != null) return false; } else if (!args.equals(other.args)) return false; if (base == null) { if (other.base != null) return false; } else if (!base.equals(other.base)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/BuiltInConstructorSymbolBase.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType; import java.util.Arrays; import java.util.List; public enum BuiltInConstructorSymbolBase implements FunctorBase { // Bit vectors BV_SLT("bv_slt", 2, ParamKind.NAT), BV_SLE("bv_sle", 2, ParamKind.NAT), BV_SGT("bv_sgt", 2, ParamKind.NAT), BV_SGE("bv_sge", 2, ParamKind.NAT), BV_ULT("bv_ult", 2, ParamKind.NAT), BV_ULE("bv_ule", 2, ParamKind.NAT), BV_UGT("bv_ugt", 2, ParamKind.NAT), BV_UGE("bv_uge", 2, ParamKind.NAT), BV_CONST("bv_const", 1, ParamKind.NAT), BV_BIG_CONST("bv_big_const", 1, ParamKind.NAT), BV_EXTRACT("bv_extract", 3, ParamKind.NAT, ParamKind.NAT), BV_CONCAT("bv_concat", 2, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT), BV_TO_BV_SIGNED("bv_to_bv_signed", 1, ParamKind.NAT, ParamKind.NAT), BV_TO_BV_UNSIGNED("bv_to_bv_unsigned", 1, ParamKind.NAT, ParamKind.NAT), FP_TO_SBV("fp_to_sbv", 1, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT), FP_TO_UBV("fp_to_ubv", 1, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT), INT_TO_BV("int_to_bv", 1, ParamKind.NAT), BV_TO_INT("bv_to_int", 1, ParamKind.NAT), // Floating point FP_EQ("fp_eq", 2, ParamKind.NAT, ParamKind.NAT), FP_LT("fp_lt", 2, ParamKind.NAT, ParamKind.NAT), FP_LE("fp_le", 2, ParamKind.NAT, ParamKind.NAT), FP_GT("fp_gt", 2, ParamKind.NAT, ParamKind.NAT), FP_GE("fp_ge", 2, ParamKind.NAT, ParamKind.NAT), FP_IS_NAN("fp_is_nan", 1, ParamKind.NAT, ParamKind.NAT), FP_CONST("fp_const", 1, ParamKind.NAT, ParamKind.NAT), FP_BIG_CONST("fp_big_const", 1, ParamKind.NAT, ParamKind.NAT), FP_TO_FP("fp_to_fp", 1, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT), BV_TO_FP("bv_to_fp", 1, ParamKind.NAT, ParamKind.NAT, ParamKind.NAT), // Logical connectives SMT_PAT("smt_pat", 1, ParamKind.SMT_REPRESENTABLE_TYPE), SMT_WRAP_VAR("smt_wrap_var", 1, ParamKind.SMT_REPRESENTABLE_TYPE), SMT_EQ("smt_eq", 2, ParamKind.PRE_SMT_TYPE), SMT_LET("smt_let", 3, ParamKind.PRE_SMT_TYPE), // Arrays ARRAY_SELECT("array_select", 2, ParamKind.PRE_SMT_TYPE), ARRAY_DEFAULT("array_default", 1, ParamKind.PRE_SMT_TYPE), // Solver variables SMT_VAR("smt_var", 1, ParamKind.ANY_TYPE, ParamKind.PRE_SMT_TYPE), ; private final String name; private final int arity; private final List paramTypes; private BuiltInConstructorSymbolBase(String name, int arity, ParamKind... paramTypes) { this.name = name; this.arity = arity; this.paramTypes = Arrays.asList(paramTypes); } @Override public int getArity() { return arity; } @Override public String toString() { return name; } @Override public List getParamKinds() { return paramTypes; } public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.SOLVER_EXPR; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/FunctorBase.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; public interface FunctorBase extends SymbolBase {} ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/Param.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import edu.harvard.seas.pl.formulog.types.Types; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import java.util.ArrayList; import java.util.List; import java.util.Map; public class Param { private final Type type; private final ParamKind kind; public Param(Type type, ParamKind kind) { this.type = type; this.kind = kind; if (!check()) { throw new IllegalArgumentException( "Cannot instantiate parameter of kind " + kind + " with type " + type); } } private boolean check() { if (type.isVar()) { return true; } switch (kind) { case ANY_TYPE: return !type.isIndex(); case WILD_CARD: return true; case SMT_REPRESENTABLE_TYPE: return Types.isSmtRepresentable(type); case NAT: return type.isIndex(); case PRE_SMT_TYPE: return Types.mayBePreSmtType(type); case SMT_VAR: return Types.isSmtVarType(type); case SMT_VARS: if (Types.isSmtVarType(type)) { return true; } if (!Types.isTupleType(type)) { return false; } for (Type typeArg : ((AlgebraicDataType) type).getTypeArgs()) { if (!typeArg.isVar() && !Types.isSmtVarType(type)) { return false; } } return true; } throw new AssertionError("impossible"); } public Type getType() { return type; } public ParamKind getKind() { return kind; } boolean isGround() { return !Types.containsTypeVarOrOpaqueType(getType()); } public static List wildCards(int howMany) { List params = new ArrayList<>(); for (int i = 0; i < howMany; ++i) { params.add(wildCard()); } return params; } public static List applySubst(Iterable params, Map subst) { List newParams = new ArrayList<>(); for (Param param : params) { newParams.add(new Param(param.getType().applySubst(subst), param.getKind())); } return newParams; } public static Param nat(int index) { return new Param(TypeIndex.make(index), ParamKind.NAT); } public static Param nat(Type type) { return new Param(type, ParamKind.NAT); } public static Param wildCard() { return new Param(TypeVar.fresh(), ParamKind.WILD_CARD); } public static Param wildCard(Type type) { return new Param(type, ParamKind.WILD_CARD); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((kind == null) ? 0 : kind.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Param other = (Param) obj; if (kind != other.kind) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; return true; } @Override public String toString() { return type + ":" + kind; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/ParamKind.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; public enum ParamKind { NAT, ANY_TYPE, SMT_REPRESENTABLE_TYPE, SMT_VAR, SMT_VARS, PRE_SMT_TYPE, WILD_CARD; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/ParameterizedConstructorSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.array; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bool; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.bv; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp32; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.fp64; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.i32; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.i64; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.int_; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smt; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smtPattern; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.smtWrappedVar; import static edu.harvard.seas.pl.formulog.types.BuiltInTypes.sym; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbolType; import edu.harvard.seas.pl.formulog.types.FunctorType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ParameterizedConstructorSymbol extends AbstractParameterizedSymbol implements ConstructorSymbol { private final FunctorType type; private static final Map< Pair>, ParameterizedConstructorSymbol> memo = new ConcurrentHashMap<>(); private ParameterizedConstructorSymbol(BuiltInConstructorSymbolBase base, List args) { super(base, args); this.type = makeType(); } public static ParameterizedConstructorSymbol mk( BuiltInConstructorSymbolBase base, List args) { switch (base) { case ARRAY_DEFAULT: case ARRAY_SELECT: case BV_BIG_CONST: case BV_CONST: case BV_SGE: case BV_SGT: case BV_SLE: case BV_SLT: case BV_TO_BV_SIGNED: case BV_TO_BV_UNSIGNED: case BV_UGE: case BV_UGT: case BV_ULE: case BV_ULT: case BV_CONCAT: case BV_EXTRACT: case SMT_EQ: case SMT_LET: case SMT_PAT: case SMT_WRAP_VAR: case SMT_VAR: case BV_TO_INT: case INT_TO_BV: break; case FP_BIG_CONST: case FP_CONST: case FP_EQ: case FP_GE: case FP_GT: case FP_IS_NAN: case FP_LE: case FP_LT: if (args.size() == 1) { args = new ArrayList<>(expandAsFpAlias(args.get(0))); } break; case FP_TO_SBV: case FP_TO_UBV: if (args.size() == 2) { Param bv = args.get(1); args = new ArrayList<>(expandAsFpAlias(args.get(0))); args.add(bv); } break; case FP_TO_FP: if (args.size() == 2) { Param fp1 = args.get(0); Param fp2 = args.get(1); args = new ArrayList<>(expandAsFpAlias(fp1)); args.addAll(expandAsFpAlias(fp2)); } break; case BV_TO_FP: if (args.size() == 2) { List newArgs = new ArrayList<>(); newArgs.add(args.get(0)); newArgs.addAll(expandAsFpAlias(args.get(1))); args = newArgs; } break; default: throw new AssertionError("Unexpected symbol: " + base); } if (args.isEmpty()) { args = Param.wildCards(base.getNumParams()); } List args2 = List.copyOf(args); return new ParameterizedConstructorSymbol(base, args2); } private static List expandAsFpAlias(Param param) { if (!param.getKind().equals(ParamKind.NAT) || !param.isGround()) { return Collections.singletonList(param); } TypeIndex nat = (TypeIndex) param.getType(); List indices = nat.expandAsFpIndex(); List params = new ArrayList<>(); for (TypeIndex index : indices) { params.add(Param.nat(index.getIndex())); } return params; } @Override public ParameterizedConstructorSymbol copyWithNewArgs(List args) { return mk(getBase(), args); } @Override public ParameterizedConstructorSymbol copyWithNewArgs(Param... args) { return copyWithNewArgs(Arrays.asList(args)); } public ConstructorSymbolType getConstructorSymbolType() { return ConstructorSymbolType.SOLVER_EXPR; } private FunctorType makeType() { List types = new ArrayList<>(); for (Param param : getArgs()) { types.add(param.getType()); } switch (getBase()) { case ARRAY_DEFAULT: { Type a = types.get(0); Type b = TypeVar.fresh(); return mkType(smt(array(a, b)), smt(b)); } case ARRAY_SELECT: { Type a = types.get(0); Type b = TypeVar.fresh(); return mkType(smt(array(a, b)), smt(a), smt(b)); } case BV_BIG_CONST: { Type width = types.get(0); return mkType(i64, smt(bv(width))); } case BV_CONST: { Type width = types.get(0); return mkType(i32, smt(bv(width))); } case BV_CONCAT: { Type w1 = types.get(0); Type w2 = types.get(1); Type w3 = types.get(2); return mkType(smt(bv(w1)), smt(bv(w2)), smt(bv(w3))); } case BV_EXTRACT: { Type w1 = types.get(0); Type w2 = types.get(1); return mkType(smt(bv(w1)), i32, i32, smt(bv(w2))); } case INT_TO_BV: return mkType(smt(int_), smt(bv(types.get(0)))); case BV_TO_INT: return mkType(smt(bv(types.get(0))), smt(int_)); case BV_SGE: case BV_SGT: case BV_SLE: case BV_SLT: case BV_UGE: case BV_UGT: case BV_ULE: case BV_ULT: { Type width = types.get(0); return mkType(smt(bv(width)), smt(bv(width)), smt(bool)); } case BV_TO_BV_SIGNED: case BV_TO_BV_UNSIGNED: { Type fromWidth = types.get(0); Type toWidth = types.get(1); return mkType(smt(bv(fromWidth)), smt(bv(toWidth))); } case BV_TO_FP: { Type width = types.get(0); Type exponent = types.get(1); Type significand = types.get(2); return mkType(smt(bv(width)), smt(fp(exponent, significand))); } case FP_BIG_CONST: { Type exponent = types.get(0); Type significand = types.get(1); return mkType(fp64, smt(fp(exponent, significand))); } case FP_CONST: { Type exponent = types.get(0); Type significand = types.get(1); return mkType(fp32, smt(fp(exponent, significand))); } case FP_EQ: case FP_GE: case FP_GT: case FP_LE: case FP_LT: { Type exponent = types.get(0); Type significand = types.get(1); Type fp = fp(exponent, significand); return mkType(smt(fp), smt(fp), smt(bool)); } case FP_IS_NAN: { Type exponent = types.get(0); Type significand = types.get(1); return mkType(smt(fp(exponent, significand)), smt(bool)); } case FP_TO_SBV: case FP_TO_UBV: { Type exponent = types.get(0); Type significand = types.get(1); Type width = types.get(2); return mkType(smt(fp(exponent, significand)), smt(bv(width))); } case FP_TO_FP: { Type exp1 = types.get(0); Type sig1 = types.get(1); Type exp2 = types.get(2); Type sig2 = types.get(3); return mkType(smt(fp(exp1, sig1)), smt(fp(exp2, sig2))); } case SMT_EQ: { Type ty = types.get(0); return mkType(smt(ty), smt(ty), smt(bool)); } case SMT_LET: { Type a = types.get(0); Type b = TypeVar.fresh(); return mkType(sym(a), smt(a), smt(b), smt(b)); } case SMT_PAT: { return mkType(types.get(0), smtPattern); } case SMT_WRAP_VAR: { return mkType(sym(types.get(0)), smtWrappedVar); } case SMT_VAR: { return mkType(types.get(0), sym(types.get(1))); } } throw new AssertionError("impossible"); } @Override public FunctorType getCompileTimeType() { return type; } private static FunctorType mkType(Type... types) { return new FunctorType(types); } @Override public ParameterizedSymbol makeFinal() { return Util.lookupOrCreate( memo, new Pair<>(getBase(), getArgs()), () -> new ParameterizedConstructorSymbol(getBase(), getArgs())); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/ParameterizedSymbol.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import edu.harvard.seas.pl.formulog.symbols.Symbol; import java.util.List; public interface ParameterizedSymbol extends Symbol { SymbolBase getBase(); List getArgs(); ParameterizedSymbol copyWithNewArgs(List args); ParameterizedSymbol copyWithNewArgs(Param... args); ParameterizedSymbol makeFinal(); boolean isGround(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/symbols/parameterized/SymbolBase.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.symbols.parameterized; import java.util.List; public interface SymbolBase { int getArity(); List getParamKinds(); default int getNumParams() { return getParamKinds().size(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/BuiltInTypes.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.CMP_EQ; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.CMP_GT; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.CMP_LT; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.CONS; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.NIL; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.NONE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorSymbol.SOME; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.ARRAY_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.BOOL_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.BV; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.CMP_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.FP; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.INT_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.LIST_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.MODEL_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.OPAQUE_SET; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.OPTION_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.SMT_PATTERN_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.SMT_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.SMT_WRAPPED_VAR_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.STRING_TYPE; import static edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol.SYM_TYPE; import edu.harvard.seas.pl.formulog.symbols.BuiltInConstructorGetterSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType.ConstructorScheme; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import java.util.Arrays; import java.util.Collections; import java.util.List; public final class BuiltInTypes { private BuiltInTypes() { throw new AssertionError(); } public static final TypeVar a = TypeVar.fresh(); public static final TypeVar b = TypeVar.fresh(); public static final TypeVar c = TypeVar.fresh(); public static final TypeVar d = TypeVar.fresh(); public static final Type i32 = bv(32); public static final Type i64 = bv(64); public static final Type fp32 = fp(8, 24); public static final Type fp64 = fp(11, 53); public static final Type string = AlgebraicDataType.make(STRING_TYPE); public static final Type bool = AlgebraicDataType.make(BOOL_TYPE); public static final Type cmp = AlgebraicDataType.make(CMP_TYPE); public static final Type model = AlgebraicDataType.make(MODEL_TYPE); public static final Type int_ = AlgebraicDataType.make(INT_TYPE); public static final Type smtPattern = AlgebraicDataType.make(SMT_PATTERN_TYPE); public static final Type smtWrappedVar = AlgebraicDataType.make(SMT_WRAPPED_VAR_TYPE); static { // Only need to set constructors for types that should be interpreted as // algebraic data types in SMT-LIB. setCmpConstructors(); setListConstructors(); setOptionConstructors(); } private static void setCmpConstructors() { ConstructorScheme gt = new ConstructorScheme(CMP_GT, Collections.emptyList(), Collections.emptyList()); ConstructorScheme lt = new ConstructorScheme(CMP_LT, Collections.emptyList(), Collections.emptyList()); ConstructorScheme eq = new ConstructorScheme(CMP_EQ, Collections.emptyList(), Collections.emptyList()); AlgebraicDataType.setConstructors(CMP_TYPE, Collections.emptyList(), Arrays.asList(gt, lt, eq)); } private static void setListConstructors() { ConstructorScheme nil = new ConstructorScheme(NIL, Collections.emptyList(), Collections.emptyList()); List consGetters = Arrays.asList(BuiltInConstructorGetterSymbol.CONS_1, BuiltInConstructorGetterSymbol.CONS_2); ConstructorScheme cons = new ConstructorScheme(CONS, Arrays.asList(a, list(a)), consGetters); AlgebraicDataType.setConstructors( LIST_TYPE, Collections.singletonList(a), Arrays.asList(nil, cons)); } private static void setOptionConstructors() { ConstructorScheme none = new ConstructorScheme(NONE, Collections.emptyList(), Collections.emptyList()); List someGetters = Arrays.asList(BuiltInConstructorGetterSymbol.SOME_1); ConstructorScheme some = new ConstructorScheme(SOME, Collections.singletonList(a), someGetters); AlgebraicDataType.setConstructors( OPTION_TYPE, Collections.singletonList(a), Arrays.asList(none, some)); } public static AlgebraicDataType list(Type a) { return AlgebraicDataType.make(LIST_TYPE, Collections.singletonList(a)); } public static AlgebraicDataType option(Type a) { return AlgebraicDataType.make(OPTION_TYPE, Collections.singletonList(a)); } public static AlgebraicDataType smt(Type a) { return AlgebraicDataType.make(SMT_TYPE, Collections.singletonList(a)); } public static AlgebraicDataType sym(Type a) { return AlgebraicDataType.make(SYM_TYPE, Collections.singletonList(a)); } public static AlgebraicDataType bv(Type a) { return AlgebraicDataType.make(BV, a); } public static AlgebraicDataType bv(int width) { return bv(TypeIndex.make(width)); } public static AlgebraicDataType fp(Type a, Type b) { return AlgebraicDataType.make(FP, a, b); } public static AlgebraicDataType fp(int exponent, int significand) { return fp(TypeIndex.make(exponent), TypeIndex.make(significand)); } public static AlgebraicDataType array(Type a, Type b) { return AlgebraicDataType.make(ARRAY_TYPE, Arrays.asList(a, b)); } public static AlgebraicDataType opaqueSet(Type a) { return AlgebraicDataType.make(OPAQUE_SET, a); } public static AlgebraicDataType pair(Type a, Type b) { return AlgebraicDataType.make(GlobalSymbolManager.lookupTupleTypeSymbol(2), a, b); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/FunctorType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.OpaqueType; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeIndex; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import edu.harvard.seas.pl.formulog.types.Types.TypeVisitor; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class FunctorType implements Type { private final List argTypes; private final Type retType; public FunctorType(List argTypes, Type retType) { this.argTypes = argTypes; this.retType = retType; } public FunctorType(Type... types) { assert types.length > 0; argTypes = new ArrayList<>(Arrays.asList(types)); retType = this.argTypes.remove(types.length - 1); } public List getArgTypes() { return argTypes; } public Type getRetType() { return retType; } public FunctorType freshen() { Map subst = new HashMap<>(); TypeVisitor visitor = new TypeVisitor() { @Override public Type visit(TypeVar typeVar, Void in) { return Util.lookupOrCreate(subst, typeVar, () -> TypeVar.fresh()); } @Override public Type visit(OpaqueType opaqueType, Void in) { throw new AssertionError(); } private List processTypeList(List types) { List newTypes = new ArrayList<>(); for (Type t : types) { newTypes.add(t.accept(this, null)); } return newTypes; } @Override public Type visit(AlgebraicDataType namedType, Void in) { return AlgebraicDataType.make( namedType.getSymbol(), processTypeList(namedType.getTypeArgs())); } @Override public Type visit(TypeIndex typeIndex, Void in) { return typeIndex; } }; List newArgTypes = new ArrayList<>(); for (Type t : argTypes) { newArgTypes.add(t.accept(visitor, null)); } Type newRetType = retType.accept(visitor, null); return new FunctorType(newArgTypes, newRetType); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((argTypes == null) ? 0 : argTypes.hashCode()); result = prime * result + ((retType == null) ? 0 : retType.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FunctorType other = (FunctorType) obj; if (argTypes == null) { if (other.argTypes != null) return false; } else if (!argTypes.equals(other.argTypes)) return false; if (retType == null) { if (other.retType != null) return false; } else if (!retType.equals(other.retType)) return false; return true; } @Override public O accept(TypeVisitor visitor, I in) { throw new UnsupportedOperationException(); } @Override public Type applySubst(Map subst) { List newArgTypes = Util.map(argTypes, t -> t.applySubst(subst)); Type newRetType = retType.applySubst(subst); return new FunctorType(newArgTypes, newRetType); } @Override public String toString() { String s = "("; for (Iterator it = argTypes.iterator(); it.hasNext(); ) { s += it.next(); if (it.hasNext()) { s += ", "; } } s += ") -> " + retType; return s; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/IndexedType.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.util.List; public interface IndexedType { Type instantiate(List indices); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/TypeAlias.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.Types.Type; import edu.harvard.seas.pl.formulog.types.Types.TypeVar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class TypeAlias { private final TypeSymbol sym; private final List params; private final Type type; public TypeAlias(TypeSymbol sym, List params, Type type) { this.sym = sym; this.params = params; this.type = type; if (!sym.isAlias()) { throw new IllegalArgumentException("Cannot create an alias for non-type alias symbol " + sym); } if (this.sym.getArity() != this.params.size()) { throw new IllegalArgumentException( "Mismatch between symbol arity and number of type parameters: " + this.sym); } if (!Types.getTypeVars(this.type).containsAll(this.params)) { throw new IllegalArgumentException( "Unbound type variable in definition of type: " + this.sym); } } public static TypeAlias get(TypeSymbol sym, List params, Type type) { return new TypeAlias(sym, params, type); } public TypeSymbol getSymbol() { return sym; } public int getNumberOfParams() { return params.size(); } public Type getParam(int idx) { if (idx < 0 || idx >= params.size()) { throw new IllegalArgumentException("Out of bounds parameter: " + idx); } return params.get(idx); } @Override public String toString() { if (params.isEmpty()) { return sym.toString() + " = " + type; } if (params.size() == 1) { return params.get(0) + " " + sym + " = " + type; } StringBuilder sb = new StringBuilder('('); Iterator it = params.iterator(); sb.append(it.next()); while (it.hasNext()) { sb.append(", " + it.next()); } sb.append(") " + sym + " = " + type); return sb.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((params == null) ? 0 : params.hashCode()); result = prime * result + ((sym == null) ? 0 : sym.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TypeAlias other = (TypeAlias) obj; if (params == null) { if (other.params != null) return false; } else if (!params.equals(other.params)) return false; if (sym == null) { if (other.sym != null) return false; } else if (!sym.equals(other.sym)) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; return true; } public List getParams() { return params; } public Type instantiate(List paramTypes) { if (paramTypes.size() != params.size()) { throw new IllegalArgumentException(); } Map subst = new HashMap<>(); for (int i = 0; i < params.size(); ++i) { TypeChecker.addBinding(params.get(i), paramTypes.get(i), subst); } return type.applySubst(subst); } public Type getType() { return type; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/TypeChecker.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.*; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitorExn; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitorExn; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.FunctionDefManager; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.*; import edu.harvard.seas.pl.formulog.symbols.parameterized.Param; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.parameterized.ParameterizedSymbol; import edu.harvard.seas.pl.formulog.types.Types.*; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.util.Triple; import edu.harvard.seas.pl.formulog.util.Util; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; public class TypeChecker { private final Program prog; private WellTypedProgram outputProgram; public TypeChecker(Program prog2) { this.prog = prog2; } public synchronized WellTypedProgram typeCheck() throws TypeException { if (outputProgram != null) { return outputProgram; } ExecutorService exec = Executors.newFixedThreadPool(Main.parallelism); Map> newFacts = typeCheckFacts(exec); Map newFuncs = typeCheckFunctions(exec); FunctionDefManager dm = prog.getFunctionCallFactory().getDefManager(); for (FunctionDef func : newFuncs.values()) { dm.reregister(func); } Map> newRules = typeCheckRules(exec); UserPredicate newQuery = typeCheckQuery(); exec.shutdown(); outputProgram = new WellTypedProgram() { @Override public Set getFunctionSymbols() { return dm.getFunctionSymbols(); } @Override public Set getFactSymbols() { return Collections.unmodifiableSet(newFacts.keySet()); } @Override public Set getRuleSymbols() { return Collections.unmodifiableSet(newRules.keySet()); } @Override public FunctionDef getDef(FunctionSymbol sym) { return dm.lookup(sym); } @Override public Set getFacts(RelationSymbol sym) { if (!sym.isEdbSymbol()) { throw new IllegalArgumentException(); } if (!newFacts.containsKey(sym)) { throw new IllegalArgumentException(); } return newFacts.get(sym); } @Override public Set getRules(RelationSymbol sym) { if (!sym.isIdbSymbol()) { throw new IllegalArgumentException(); } if (!newRules.containsKey(sym)) { throw new IllegalArgumentException(); } return newRules.get(sym); } @Override public SymbolManager getSymbolManager() { return prog.getSymbolManager(); } @Override public boolean hasQuery() { return newQuery != null; } @Override public UserPredicate getQuery() { return newQuery; } @Override public FunctionCallFactory getFunctionCallFactory() { return prog.getFunctionCallFactory(); } @Override public Set getUninterpretedFunctionSymbols() { return prog.getUninterpretedFunctionSymbols(); } @Override public Set getTypeSymbols() { return prog.getTypeSymbols(); } }; return outputProgram; } private UserPredicate typeCheckQuery() throws TypeException { if (prog.hasQuery()) { TypeCheckerContext ctx = new TypeCheckerContext(); return ctx.typeCheckQuery(prog.getQuery()); } return null; } private static Map mapFromFutures(Map> futures) throws TypeException { try { return Util.fillMapWithFutures(futures, new HashMap<>()); } catch (InterruptedException | ExecutionException e) { throw new TypeException(e); } } private Map> typeCheckFacts(ExecutorService exec) throws TypeException { Map>> futures = new HashMap<>(); for (RelationSymbol sym : prog.getFactSymbols()) { Future> fut = exec.submit( new Callable>() { @Override public Set call() throws Exception { Set s = new HashSet<>(); TypeCheckerContext ctx = new TypeCheckerContext(); for (Term[] args : prog.getFacts(sym)) { s.add(ctx.typeCheckFact(sym, args)); } return s; } }); futures.put(sym, fut); } return mapFromFutures(futures); } private Map> typeCheckRules(ExecutorService exec) throws TypeException { Map>> futures = new HashMap<>(); for (RelationSymbol sym : prog.getRuleSymbols()) { Future> fut = exec.submit( new Callable>() { @Override public Set call() throws Exception { Set s = new HashSet<>(); for (BasicRule r : prog.getRules(sym)) { TypeCheckerContext ctx = new TypeCheckerContext(); s.add(ctx.typeCheckRule(r)); } return s; } }); futures.put(sym, fut); } return mapFromFutures(futures); } private Map typeCheckFunctions(ExecutorService exec) throws TypeException { Map> futures = new HashMap<>(); List nonUserFunctions = new ArrayList<>(); for (FunctionSymbol sym : prog.getFunctionSymbols()) { FunctionDef def = prog.getDef(sym); if (def instanceof UserFunctionDef) { Future fut = exec.submit( () -> { TypeCheckerContext ctx = new TypeCheckerContext(); try { return ctx.typeCheckFunction((UserFunctionDef) def); } catch (Throwable e) { throw new TypeException( "Problem type checking function: " + sym + "\n" + e.getMessage()); } }); futures.put(sym, fut); } else { nonUserFunctions.add(def); } } Map m = mapFromFutures(futures); for (FunctionDef def : nonUserFunctions) { m.put(def.getSymbol(), def); } return m; } private class TypeCheckerContext { private final Deque> constraints = new ArrayDeque<>(); private final Deque> formulaConstraints = new ArrayDeque<>(); private final Map typeVars = new HashMap<>(); private String error; private Term rewriteTerm(Term t, Substitution m) throws TypeException { return t.accept(termRewriter, m); } public Term[] typeCheckFact(RelationSymbol sym, Term[] args) throws TypeException { Map subst = new HashMap<>(); genConstraints(sym, args, subst); if (!checkConstraints()) { throw new TypeException( "Type error in fact: " + UserPredicate.make(sym, args, false) + "\n" + error); } Substitution m = makeIndexSubstitution(subst); try { return Terms.mapExn(args, t -> rewriteTerm(t, m)); } catch (TypeException e) { throw new TypeException( "Problem with rewriting fact: " + UserPredicate.make(sym, args, false) + "\n" + e.getMessage()); } } private ComplexLiteral rewriteLiteral(ComplexLiteral l, Substitution m, Map env) throws TypeException { try { Term[] args = Terms.mapExn(l.getArgs(), t -> rewriteTerm(t, m)); return l.accept( new ComplexLiteralExnVisitor() { @Override public ComplexLiteral visit(UnificationPredicate pred, Void input) throws TypeException { return UnificationPredicate.make(args[0], args[1], pred.isNegated()); } @Override public ComplexLiteral visit(UserPredicate pred, Void input) throws TypeException { return UserPredicate.make(pred.getSymbol(), args, pred.isNegated()); } }, null); } catch (TypeException e) { throw new TypeException("Problem with rewriting literal: " + l + "\n" + e.getMessage()); } } public UserPredicate typeCheckQuery(UserPredicate q) throws TypeException { Map subst = new HashMap<>(); genConstraints(q, subst); if (!checkConstraints()) { throw new TypeException("Type error in query: " + q + "\n" + error); } Substitution m = makeIndexSubstitution(subst); try { return (UserPredicate) rewriteLiteral(q, m, subst); } catch (TypeException e) { throw new TypeException("Problem with rewriting query: " + q + "\n" + e.getMessage()); } } public BasicRule typeCheckRule(Rule r) throws TypeException { Map subst = new HashMap<>(); processAtoms(r, subst); genConstraints(r.getHead(), subst); if (!checkConstraints()) { String msg = "Type error in rule:\n"; msg += r + "\n"; msg += error; throw new TypeException(msg); } Substitution m = makeIndexSubstitution(subst); UserPredicate newHead = (UserPredicate) rewriteLiteral(r.getHead(), m, subst); try { List newBody = new ArrayList<>(); for (ComplexLiteral a : r) { newBody.add(rewriteLiteral(a, m, subst)); } return BasicRule.make(newHead, newBody); } catch (TypeException e) { throw new TypeException("Problem with rewriting rule:\n" + r + "\n" + e.getMessage()); } } private Substitution makeIndexSubstitution(Map subst) { Substitution m = new SimpleSubstitution(); for (Map.Entry e : subst.entrySet()) { Var x = e.getKey(); Type t = lookupType(e.getValue()); if (t instanceof TypeIndex) { int idx = ((TypeIndex) t).getIndex(); ConstructorSymbol csym = prog.getSymbolManager().lookupIndexConstructorSymbol(idx); Term c = Constructors.make(csym, Terms.singletonArray(I32.make(idx))); m.put(x, c); } } return m; } public UserFunctionDef typeCheckFunction(UserFunctionDef functionDef) throws TypeException { Map subst = new HashMap<>(); genConstraints(functionDef, subst); if (!checkConstraints()) { throw new TypeException( "Type error in function: " + functionDef.getSymbol() + "\n" + functionDef.getBody() + "\n" + error); } Substitution m = makeIndexSubstitution(subst); try { return UserFunctionDef.get( functionDef.getSymbol(), functionDef.getParams(), rewriteTerm(functionDef.getBody(), m)); } catch (Throwable e) { throw new TypeException( "Problem with rewriting the function: " + functionDef.getSymbol() + "\n" + functionDef.getBody() + "\n" + e.getMessage()); } } private void processAtoms(Iterable atoms, Map subst) { for (ComplexLiteral a : atoms) { genConstraints(a, subst); } } private void genConstraints(RelationSymbol sym, Term[] args, Map subst) { FunctorType ftype = sym.getCompileTimeType().freshen(); assert ftype.getArgTypes().size() == args.length; int i = 0; for (Type type : ftype.getArgTypes()) { genConstraints(args[i], type, subst, false); ++i; } } private void genConstraints(ComplexLiteral a, Map subst) { a.accept( new ComplexLiteralVisitor() { @Override public Void visit(UserPredicate normalAtom, Void in) { genConstraints(normalAtom.getSymbol(), normalAtom.getArgs(), subst); return null; } @Override public Void visit(UnificationPredicate unifyAtom, Void in) { TypeVar var = TypeVar.fresh(); genConstraints(unifyAtom.getLhs(), var, subst, false); genConstraints(unifyAtom.getRhs(), var, subst, false); return null; } }, null); } private void genConstraints(UserFunctionDef def, Map subst) { FunctionSymbol sym = def.getSymbol(); genConstraints(sym, def.getParams(), def.getBody(), subst); } private void genConstraints( FunctionSymbol sym, List params, Term body, Map subst) { FunctorType scheme = sym.getCompileTimeType().freshen(); Map opaqueTypes = new HashMap<>(); List argTypes = scheme.getArgTypes(); for (Type t : argTypes) { addConstraint(null, t, mkTypeVarsOpaque(t, opaqueTypes), false); } Type r = scheme.getRetType(); addConstraint(null, r, mkTypeVarsOpaque(r, opaqueTypes), false); for (int i = 0; i < params.size(); ++i) { subst.put(params.get(i), argTypes.get(i)); } genConstraints(body, r, subst, false); } private Type mkTypeVarsOpaque(Type t, Map subst) { return t.accept( new TypeVisitor() { @Override public Type visit(TypeVar typeVar, Void in) { return Util.lookupOrCreate(subst, typeVar, OpaqueType::get); } @Override public Type visit(AlgebraicDataType algebraicType, Void in) { List newArgs = algebraicType.getTypeArgs().stream() .map(t -> t.accept(this, null)) .collect(Collectors.toList()); return AlgebraicDataType.make(algebraicType.getSymbol(), newArgs); } @Override public Type visit(OpaqueType opaqueType, Void in) { return opaqueType; } @Override public Type visit(TypeIndex typeIndex, Void in) { return typeIndex; } }, null); } private void genConstraintsForExpr( Expr e, Type exprType, Map varTypes, boolean inFormula) { e.accept( new ExprVisitor() { @Override public Void visit(MatchExpr matchExpr, Void in) { assert !inFormula; TypeVar guardType = TypeVar.fresh(); Term guard = matchExpr.getMatchee(); genConstraints(guard, guardType, varTypes, inFormula); for (MatchClause cl : matchExpr) { genConstraints(cl.getLhs(), guardType, varTypes, inFormula); genConstraints(cl.getRhs(), exprType, varTypes, inFormula); } return null; } @Override public Void visit(FunctionCall funcCall, Void in) { genConstraintsForFunctionCall(funcCall, exprType, varTypes, inFormula); return null; } @Override public Void visit(LetFunExpr letFun, Void in) { assert !inFormula; for (NestedFunctionDef def : letFun.getDefs()) { def = def.freshen(); genConstraints(def.getSymbol(), def.getParams(), def.getBody(), varTypes); } genConstraints(letFun.getLetBody(), exprType, varTypes, inFormula); return null; } @Override public Void visit(Fold fold, Void in) { assert !inFormula; FunctionSymbol sym = fold.getFunction(); Term[] args = fold.getArgs(); assert sym.getArity() == args.length && args.length == 2; Type itemType = TypeVar.fresh(); Type listType = BuiltInTypes.list(itemType); FunctorType funType = sym.getCompileTimeType().freshen(); genConstraints(args[0], exprType, varTypes, inFormula); genConstraints(args[0], funType.getArgTypes().get(0), varTypes, inFormula); genConstraints(args[1], listType, varTypes, inFormula); addConstraint(args[1], itemType, funType.getArgTypes().get(1), inFormula); addConstraint(fold, exprType, funType.getRetType(), inFormula); return null; } }, null); } private void addConstraint(Term t, Type t1, Type t2, boolean inFormula) { Triple constraint = new Triple<>(t, t1, t2); if (inFormula) { formulaConstraints.add(constraint); } else { constraints.add(constraint); } } private void genConstraints(Term t, Type ttype, Map subst, boolean inFormula) { t.accept( new TermVisitor() { @Override public Void visit(Var t, Void in) { genConstraintsForVar(t, ttype, subst, inFormula); return null; } @Override public Void visit(Constructor t, Void in) { genConstraintsForConstructor(t, ttype, subst, inFormula); return null; } @Override public Void visit(Primitive prim, Void in) { genConstraintsForPrimitive(prim, ttype, inFormula); return null; } @Override public Void visit(Expr expr, Void in) { genConstraintsForExpr(expr, ttype, subst, inFormula); return null; } }, null); } private void genConstraintsForVar(Var t, Type ttype, Map subst, boolean inFormula) { Type s = Util.lookupOrCreate( subst, t, () -> { Type x = TypeVar.fresh(); if (inFormula) { x = BuiltInTypes.smt(x); } return x; }); addConstraint(t, s, ttype, inFormula); } private void genConstraintsForPrimitive(Primitive t, Type ttype, boolean inFormula) { addConstraint(t, ttype, t.getType().freshen(), inFormula); } private void genConstraintsForConstructor( Constructor t, Type ttype, Map subst, boolean inFormula) { boolean wasInFormula = inFormula; ConstructorSymbol cnstrSym = t.getSymbol(); if (cnstrSym.equals(BuiltInConstructorSymbol.ENTER_FORMULA)) { assert !wasInFormula; inFormula = true; } if (cnstrSym.equals(BuiltInConstructorSymbol.EXIT_FORMULA)) { inFormula = false; } FunctorType cnstrType = cnstrSym.getCompileTimeType(); if (!(cnstrSym instanceof ParameterizedConstructorSymbol)) { cnstrType = cnstrType.freshen(); } Term[] args = t.getArgs(); List argTypes = cnstrType.getArgTypes(); for (int i = 0; i < args.length; ++i) { Type argType = argTypes.get(i); genConstraints(args[i], argType, subst, inFormula); } addConstraint(t, cnstrType.getRetType(), ttype, wasInFormula); } private void genConstraintsForFunctionCall( FunctionCall function, Type ttype, Map subst, boolean inFormula) { FunctorType funType = (FunctorType) function.getSymbol().getCompileTimeType().freshen(); Term[] args = function.getArgs(); List argTypes = funType.getArgTypes(); for (int i = 0; i < args.length; ++i) { genConstraints(args[i], argTypes.get(i), subst, inFormula); } addConstraint(function, funType.getRetType(), ttype, inFormula); } private boolean checkConstraints() { Set typesInFormulae = new HashSet<>(); for (Triple p : formulaConstraints) { typesInFormulae.add(p.second); } // First try to unify constraints generated outside of formula and then // constraints generated inside formula. This might generate more constraints, // but all of them will be treated as being in a non-formula context. boolean ok = checkConstraints(false) && checkConstraints(true) && checkConstraints(false); assert !ok || constraints.isEmpty() && formulaConstraints.isEmpty(); ok = ok && checkTypesInFormulae(typesInFormulae); return ok; } private boolean checkTypesInFormulae(Set types) { for (Type ty : types) { if (!checkTypeInFormula(ty)) { return false; } } return true; } private boolean checkTypeInFormula(Type ty) { ty = lookupType(ty); if (!Types.isSmtRepresentable(ty)) { error = "Terms of the following type are not allowed in formulas: " + ty + "\n" + "The following types can lead to this error: model, smt_pattern, smt_wrapped_var," + " and type variables that are not proven to contain a safe type"; return false; } return true; } private boolean checkConstraints(boolean inFormulaContext) { Deque> q = inFormulaContext ? formulaConstraints : constraints; while (!q.isEmpty()) { Triple constraint = q.pop(); Type type1 = lookupType(constraint.second); Type type2 = lookupType(constraint.third); if (inFormulaContext) { type1 = simplify(type1); type2 = simplify(type2); } if (type1.isVar() || type2.isVar()) { if (!handleVars(type1, type2)) { return false; } } else if (!unify(constraint.first, type1, type2)) { error = "Cannot unify " + type1 + " and " + type2; error += "\nProblematic term: " + constraint.first; return false; } } return true; } private boolean handleVars(Type type1, Type type2) { if (type1.isVar() && type2.isVar()) { addBinding((TypeVar) type1, type2, typeVars); return true; } TypeVar var; Type other; if (type1.isVar()) { var = (TypeVar) type1; other = type2; } else { assert type2.isVar(); var = (TypeVar) type2; other = type1; } // Occurs check if (Types.getTypeVars(other).contains(var)) { return false; } typeVars.put(var, other); return true; } private boolean unify(Term t, Type type1, Type type2) { return type1.accept( new TypeVisitor() { @Override public Boolean visit(TypeVar typeVar, Type other) { throw new AssertionError("unreachable"); } @Override public Boolean visit(AlgebraicDataType typeRef, Type other) { if (!(other instanceof AlgebraicDataType)) { return false; } AlgebraicDataType otherTypeRef = (AlgebraicDataType) other; if (!typeRef.getSymbol().equals(otherTypeRef.getSymbol())) { return false; } List args1 = typeRef.getTypeArgs(); List args2 = otherTypeRef.getTypeArgs(); for (int i = 0; i < args1.size(); ++i) { addConstraint(t, args1.get(i), args2.get(i), false); } return true; } @Override public Boolean visit(OpaqueType opaqueType, Type other) { return opaqueType.equals(other); } @Override public Boolean visit(TypeIndex typeIndex, Type other) { return typeIndex.equals(other); } }, type2); } private Type lookupType(Type t) { return TypeChecker.lookupType(t, typeVars); } private final TermVisitorExn termRewriter = new TermVisitorExn() { @Override public Term visit(Var x, Substitution subst) throws TypeException { if (subst.containsKey(x)) { return subst.get(x); } return x; } @Override public Term visit(Constructor c, Substitution subst) throws TypeException { ConstructorSymbol sym = c.getSymbol(); if (sym.equals(BuiltInConstructorSymbol.ENTER_FORMULA) || sym.equals(BuiltInConstructorSymbol.EXIT_FORMULA)) { return c.getArgs()[0].accept(this, subst); } if (sym instanceof ParameterizedConstructorSymbol) { try { sym = (ConstructorSymbol) handleParameterizedSymbol((ParameterizedConstructorSymbol) sym); } catch (TypeException e) { throw new TypeException( "Problem with rewriting term " + c + ":\n" + e.getMessage()); } } Term[] args = c.getArgs(); Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].accept(this, subst); } return Constructors.make(sym, newArgs); } @Override public Term visit(Primitive p, Substitution subst) throws TypeException { return p; } @Override public Term visit(Expr e, Substitution subst) throws TypeException { return e.accept(exprRewriter, subst); } }; private ParameterizedSymbol handleParameterizedSymbol(ParameterizedSymbol sym) throws TypeException { List params = sym.getArgs(); params = Param.applySubst(params, typeVars); for (Param param : params) { if (Types.containsTypeVarOrOpaqueType(param.getType())) { throw new TypeException( "Cannot instantiate parameterized symbol " + sym + " with a parameter that is not ground: " + param); } } return sym.copyWithNewArgs(params).makeFinal(); } private final Map topLevelSymbolOfNestedFunction = new HashMap<>(); private final Map> capturedVarsOfNestedFunction = new HashMap<>(); private final ExprVisitorExn exprRewriter = new ExprVisitorExn() { @Override public Term visit(MatchExpr matchExpr, Substitution subst) throws TypeException { Term scrutinee = matchExpr.getMatchee().accept(termRewriter, subst); List clauses = new ArrayList<>(); for (MatchClause cl : matchExpr) { Term lhs = cl.getLhs().accept(termRewriter, subst); Term rhs = cl.getRhs().accept(termRewriter, subst); clauses.add(MatchClause.make(lhs, rhs)); } return MatchExpr.make(scrutinee, clauses); } @Override public Term visit(FunctionCall funcCall, Substitution subst) throws TypeException { FunctionSymbol sym = funcCall.getSymbol(); List capturedVars = Collections.emptyList(); if (topLevelSymbolOfNestedFunction.containsKey(sym)) { sym = topLevelSymbolOfNestedFunction.get(sym); capturedVars = capturedVarsOfNestedFunction.get(sym); } Term[] args = funcCall.getArgs(); Term[] newArgs = new Term[args.length + capturedVars.size()]; int i = 0; for (; i < args.length; ++i) { newArgs[i] = args[i].accept(termRewriter, subst); } for (Var x : capturedVars) { newArgs[i] = x; ++i; } return prog.getFunctionCallFactory().make(sym, newArgs); } @Override public Term visit(LetFunExpr letFun, Substitution in) throws TypeException { Set defs = new HashSet<>(); Set capturedVarSet = new HashSet<>(); for (NestedFunctionDef def : letFun.getDefs()) { def = firstRewritingPass(def, in); defs.add(def); Set s = def.getBody().varSet(); s.removeAll(def.getParams()); capturedVarSet.addAll(s); } List capturedVars = new ArrayList<>(capturedVarSet); for (NestedFunctionDef def : defs) { FunctionSymbol oldSym = def.getSymbol(); int newArity = oldSym.getArity() + capturedVarSet.size(); FunctionSymbol newSym = prog.getSymbolManager() .createFunctionSymbol(oldSym + "$toplevel", newArity, null); topLevelSymbolOfNestedFunction.put(oldSym, newSym); capturedVarsOfNestedFunction.put(newSym, capturedVars); } for (NestedFunctionDef def : defs) { FunctionSymbol sym = topLevelSymbolOfNestedFunction.get(def.getSymbol()); List params = new ArrayList<>(def.getParams()); params.addAll(capturedVars); FunctionDef newDef = UserFunctionDef.get(sym, params, def.getBody().accept(termRewriter, in)); prog.getFunctionCallFactory().getDefManager().register(newDef); } return letFun.getLetBody().accept(termRewriter, in); } NestedFunctionDef firstRewritingPass(NestedFunctionDef def, Substitution subst) throws TypeException { Substitution s = new SimpleSubstitution(); List newParams = new ArrayList<>(); for (Var param : def.getParams()) { if (!s.containsKey(param)) { Var newParam = Var.fresh(param.toString()); s.put(param, newParam); newParams.add(newParam); } else { newParams.add((Var) s.get(param)); } } Term body = def.getBody().applySubstitution(s).accept(termRewriter, subst); return NestedFunctionDef.make(def.getSymbol(), newParams, body); } @Override public Term visit(Fold fold, Substitution in) throws TypeException { FunctionCall f = (FunctionCall) fold.getShamCall().accept(this, in); return Fold.mk(f.getSymbol(), f.getArgs(), f.getFactory()); } }; } // XXX This and lookupType should be factored into a class for type // substitutions. public static void addBinding(TypeVar x, Type t, Map subst) { if (t instanceof TypeVar) { // Avoid cycles in map TypeVar y = (TypeVar) t; if (x.compareTo(y) > 0) { subst.put(x, y); } else if (x.compareTo(y) < 0) { subst.put(y, x); } } else { subst.put(x, t); } } public static Type lookupType(Type t, Map subst) { return t.accept( new TypeVisitor() { @Override public Type visit(TypeVar typeVar, Void in) { if (subst.containsKey(typeVar)) { return subst.get(typeVar).accept(this, in); } return typeVar; } @Override public Type visit(AlgebraicDataType algebraicType, Void in) { List args = Util.map(algebraicType.getTypeArgs(), t -> t.accept(this, in)); return AlgebraicDataType.make(algebraicType.getSymbol(), args); } @Override public Type visit(OpaqueType opaqueType, Void in) { return opaqueType; } @Override public Type visit(TypeIndex typeIndex, Void in) { return typeIndex; } }, null); } // Simplify type: make it a non-smt, non-sym type. public static Type simplify(Type t) { return t.accept( new TypeVisitor() { @Override public Type visit(TypeVar typeVar, Void in) { return typeVar; } @Override public Type visit(AlgebraicDataType algebraicType, Void in) { TypeSymbol sym = algebraicType.getSymbol(); List args = algebraicType.getTypeArgs(); if (sym.equals(BuiltInTypeSymbol.SMT_TYPE) || sym.equals(BuiltInTypeSymbol.SYM_TYPE)) { return args.get(0).accept(this, in); } args = Util.map(args, t -> t.accept(this, in)); return AlgebraicDataType.make(sym, args); } @Override public Type visit(OpaqueType opaqueType, Void in) { return opaqueType; } @Override public Type visit(TypeIndex typeIndex, Void in) { return typeIndex; } }, null); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/TypeException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; public class TypeException extends Exception { private static final long serialVersionUID = -7218535057319510121L; /** Constructs an exception signifying a type error. */ public TypeException() {} /** * Constructs an exception signifying a type error. * * @param message the error message */ public TypeException(String message) { super(message); } /** * Constructs an exception signifying a type error. * * @param cause the exception that caused this exception */ public TypeException(Throwable cause) { super(cause); } /** * Constructs an exception signifying a type error. * * @param message the error message * @param cause the exception that caused this exception */ public TypeException(String message, Throwable cause) { super(message, cause); } /** * Constructs an exception signifying a type error. * * @param message the error message * @param cause the exception that caused this exception * @param enableSuppression whether or not suppression is enabled or disabled * @param writableStackTrace whether or not the stack trace should be writable */ public TypeException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/TypeManager.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.types.Types.AlgebraicDataType; import edu.harvard.seas.pl.formulog.types.Types.Type; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; public class TypeManager { private final Map aliases = new HashMap<>(); public void registerAlias(TypeAlias alias) { TypeAlias alias2 = aliases.putIfAbsent(alias.getSymbol(), alias); if (alias2 != null && !alias2.equals(alias)) { throw new IllegalArgumentException( "Cannot register " + alias.getSymbol() + " as aliasing multiple types."); } } public Type lookup(TypeSymbol typeSym, List typeArgs) { if (!typeSym.isAlias()) { return AlgebraicDataType.make(typeSym, typeArgs); } TypeAlias alias = aliases.get(typeSym); if (alias == null) { throw new NoSuchElementException("No type associated with symbol " + typeSym); } return alias.instantiate(typeArgs); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/Types.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.symbols.BuiltInTypeSymbol; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.symbols.GlobalSymbolManager; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.symbols.TypeSymbol; import edu.harvard.seas.pl.formulog.util.Pair; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; public final class Types { private Types() { throw new AssertionError(); } public interface Type { O accept(TypeVisitor visitor, I in); Type applySubst(Map subst); Type freshen(); default boolean isVar() { return false; } default boolean isIndex() { return false; } } public interface TypeVisitor { O visit(TypeVar typeVar, I in); O visit(AlgebraicDataType algebraicType, I in); O visit(OpaqueType opaqueType, I in); O visit(TypeIndex typeIndex, I in); } public static class TypeVar implements Type, Comparable { private static final Map memo = new ConcurrentHashMap<>(); private static final AtomicInteger cnt = new AtomicInteger(); private final int id; private TypeVar(int id) { this.id = id; } public static TypeVar get(String id) { int i = Util.lookupOrCreate(memo, id, cnt::getAndIncrement); return new TypeVar(i); } @Override public String toString() { return "'_" + id; } @Override public O accept(TypeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TypeVar other = (TypeVar) obj; return id == other.id; } @Override public Type applySubst(Map subst) { return TypeChecker.lookupType(this, subst); } public static TypeVar fresh() { return new TypeVar(cnt.getAndIncrement()); } @Override public boolean isVar() { return true; } @Override public int compareTo(TypeVar o) { return Integer.compare(this.id, o.id); } @Override public Type freshen() { return fresh(); } } private static List freshen(List types) { return types.stream().map(Type::freshen).collect(Collectors.toList()); } public static class AlgebraicDataType implements Type, Iterable { private final TypeSymbol sym; private final List typeArgs; private final AtomicReference> constructors = new AtomicReference<>(); private static final Map, Set>> memo = new ConcurrentHashMap<>(); private AlgebraicDataType(TypeSymbol sym, List typeArgs) { this.sym = sym; this.typeArgs = new ArrayList<>(typeArgs); check(); } private void check() { if (sym.getArity() != typeArgs.size()) { throw new IllegalArgumentException( "Arity of symbol " + sym + " (" + sym.getArity() + ") does not match number of provided type parameters: " + typeArgs); } if (sym.isAlias()) { throw new IllegalArgumentException("Cannot create a type with alias symbol " + sym); } if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case ARRAY_TYPE: case BOOL_TYPE: case CMP_TYPE: case INT_TYPE: case LIST_TYPE: case MODEL_TYPE: case OPTION_TYPE: case SMT_PATTERN_TYPE: case SMT_WRAPPED_VAR_TYPE: case STRING_TYPE: case OPAQUE_SET: break; case BV: case FP: for (Type arg : typeArgs) { if (!arg.isVar() && !(arg instanceof TypeIndex)) { throw new IllegalArgumentException( "Cannot instantiate built-in type " + sym + " with type argument " + arg); } } break; case SMT_TYPE: case SYM_TYPE: for (Type arg : typeArgs) { if (!mayBePreSmtType(arg)) { throw new IllegalArgumentException( "Cannot instantiate built-in type " + sym + " with type argument " + arg); } } break; default: throw new AssertionError("impossible"); } } } public static AlgebraicDataType makeWithFreshArgs(TypeSymbol sym) { List typeArgs = new ArrayList<>(); for (int i = 0; i < sym.getArity(); ++i) { typeArgs.add(TypeVar.fresh()); } return make(sym, typeArgs); } public static AlgebraicDataType make(TypeSymbol sym, Type... typeArgs) { return make(sym, Arrays.asList(typeArgs)); } public static AlgebraicDataType make(TypeSymbol sym, List typeArgs) { return new AlgebraicDataType(sym, typeArgs); } public static void setConstructors( TypeSymbol sym, List typeParams, Collection constructors) { if (sym.isAlias()) { throw new IllegalArgumentException( "Cannot set constructors for type alias symbol " + sym + "."); } if (typeParams.size() != (new HashSet<>(typeParams).size())) { throw new IllegalArgumentException("Each type variable must be unique."); } if (memo.put(sym, new Pair<>(typeParams, new HashSet<>(constructors))) != null) { throw new IllegalStateException("Cannot set the constructors for a type multiple times."); } } public boolean hasConstructors() { return memo.containsKey(sym); } public Set getConstructors() { Set s = constructors.get(); if (s == null) { Pair, Set> p = memo.get(sym); if (p == null) { throw new IllegalStateException("No constructors have been set for symbol " + sym + "."); } List params = p.fst(); Map subst = new HashMap<>(); for (int i = 0; i < params.size(); ++i) { TypeVar x = params.get(i); Type t = typeArgs.get(i); // XXX This is to avoid cycles in subst, but is obviously fragile if (!x.equals(t)) { subst.put(x, t); } } s = new HashSet<>(); for (ConstructorScheme c : p.snd()) { List newArgs = new ArrayList<>(); for (Type t : c.getTypeArgs()) { newArgs.add(t.applySubst(subst)); } s.add(new ConstructorScheme(c.getSymbol(), newArgs, c.getGetterSymbols())); } if (!constructors.compareAndSet(null, s)) { s = constructors.get(); } } return s; } public TypeSymbol getSymbol() { return sym; } @Override public Iterator iterator() { return typeArgs.iterator(); } @Override public String toString() { if (sym.equals(BuiltInTypeSymbol.BV) || sym.equals(BuiltInTypeSymbol.FP)) { return toStringBvOrFp(); } if (typeArgs.isEmpty()) { return sym.toString(); } if (typeArgs.size() == 1) { return typeArgs.get(0) + " " + sym; } StringBuilder sb = new StringBuilder("("); Iterator it = typeArgs.iterator(); sb.append(it.next()); while (it.hasNext()) { sb.append(", ").append(it.next()); } sb.append(") ").append(sym); return sb.toString(); } private String toStringBvOrFp() { StringBuilder sb = new StringBuilder(sym.toString()); sb.append("["); for (Iterator it = typeArgs.iterator(); it.hasNext(); ) { Type arg = it.next(); if (arg instanceof TypeIndex) { sb.append(((TypeIndex) arg).getIndex()); } else { assert arg.isVar(); sb.append(arg); } if (it.hasNext()) { sb.append(", "); } } return sb + "]"; } @Override public O accept(TypeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((sym == null) ? 0 : sym.hashCode()); result = prime * result + ((typeArgs == null) ? 0 : typeArgs.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AlgebraicDataType other = (AlgebraicDataType) obj; if (sym == null) { if (other.sym != null) return false; } else if (!sym.equals(other.sym)) return false; if (typeArgs == null) { return other.typeArgs == null; } else return typeArgs.equals(other.typeArgs); } public List getTypeArgs() { return typeArgs; } @Override public Type applySubst(Map subst) { List newTypes = new ArrayList<>(); for (Type t : typeArgs) { newTypes.add(t.applySubst(subst)); } return make(sym, newTypes); } @Override public Type freshen() { return make(sym, Types.freshen(typeArgs)); } public static class ConstructorScheme { private final ConstructorSymbol sym; private final List typeArgs; private final List getterSyms; public ConstructorScheme( ConstructorSymbol sym, List typeArgs, List getterSyms) { this.sym = sym; this.typeArgs = typeArgs; this.getterSyms = getterSyms; assert sym != null; } public ConstructorSymbol getSymbol() { return sym; } public List getTypeArgs() { return typeArgs; } public List getGetterSymbols() { return getterSyms; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getterSyms == null) ? 0 : getterSyms.hashCode()); result = prime * result + sym.hashCode(); result = prime * result + ((typeArgs == null) ? 0 : typeArgs.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ConstructorScheme other = (ConstructorScheme) obj; if (getterSyms == null) { if (other.getterSyms != null) return false; } else if (!getterSyms.equals(other.getterSyms)) return false; if (!sym.equals(other.sym)) return false; if (typeArgs == null) { return other.typeArgs == null; } else return typeArgs.equals(other.typeArgs); } } } public static class OpaqueType implements Type { private static final AtomicInteger cnt = new AtomicInteger(); private final int id; private OpaqueType() { id = cnt.getAndIncrement(); } public static OpaqueType get() { return new OpaqueType(); } @Override public O accept(TypeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public Type applySubst(Map subst) { return this; } @Override public String toString() { return "Opaque" + id; } @Override public Type freshen() { return get(); } } public static class TypeIndex implements Type { private final int index; private TypeIndex(int index) { this.index = index; } public static TypeIndex make(int index) { return new TypeIndex(index); } @Override public O accept(TypeVisitor visitor, I in) { return visitor.visit(this, in); } @Override public Type applySubst(Map subst) { return this; } @Override public Type freshen() { return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + index; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TypeIndex other = (TypeIndex) obj; return index == other.index; } @Override public String toString() { return "[" + index + "]"; } @Override public boolean isIndex() { return true; } public int getIndex() { return index; } public List expandAsFpIndex() { switch (index) { case 16: return Arrays.asList(make(5), make(11)); case 32: return Arrays.asList(make(8), make(24)); case 64: return Arrays.asList(make(11), make(53)); case 128: return Arrays.asList(make(15), make(113)); default: throw new IllegalArgumentException("Illegal floating point width alias: " + index); } } } // Helpers ///////////////////////////////////////////////////////////////// public static Set getTypeVars(Type t) { return getTypeVars(Collections.singletonList(t)); } public static Set getTypeVars(Collection t) { Set set = new HashSet<>(); getTypeVars(t, set); return set; } private static void getTypeVars(Type t, Set acc) { t.accept( new TypeVisitor() { @Override public Void visit(TypeVar typeVar, Void in) { acc.add(typeVar); return null; } @Override public Void visit(OpaqueType opaqueType, Void in) { return null; } @Override public Void visit(AlgebraicDataType namedType, Void in) { getTypeVars(namedType.getTypeArgs(), acc); return null; } @Override public Void visit(TypeIndex typeIndex, Void in) { return null; } }, null); } private static void getTypeVars(Collection types, Set acc) { for (Type t : types) { getTypeVars(t, acc); } } public static boolean containsTypeVarOrOpaqueType(Type t) { return t.accept( new TypeVisitor() { @Override public Boolean visit(TypeVar typeVar, Void in) { return true; } @Override public Boolean visit(AlgebraicDataType algebraicType, Void in) { for (Type ty : algebraicType.getTypeArgs()) { if (ty.accept(this, in)) { return true; } } return false; } @Override public Boolean visit(OpaqueType opaqueType, Void in) { return true; } @Override public Boolean visit(TypeIndex typeIndex, Void in) { return false; } }, null); } public static boolean isSmtRepresentable(Type t) { return !t.isIndex() && t.accept( new TypeVisitor() { @Override public Boolean visit(TypeVar typeVar, Void in) { return true; } @Override public Boolean visit(AlgebraicDataType algebraicType, Void in) { TypeSymbol sym = algebraicType.getSymbol(); if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case ARRAY_TYPE: case BOOL_TYPE: case BV: case CMP_TYPE: case FP: case INT_TYPE: case LIST_TYPE: case OPTION_TYPE: case SMT_TYPE: case STRING_TYPE: case SYM_TYPE: break; case OPAQUE_SET: case MODEL_TYPE: case SMT_PATTERN_TYPE: case SMT_WRAPPED_VAR_TYPE: return false; default: throw new AssertionError("impossible"); } } for (Type typeArg : algebraicType.getTypeArgs()) { if (!typeArg.accept(this, in)) { return false; } } return true; } @Override public Boolean visit(OpaqueType opaqueType, Void in) { return false; } @Override public Boolean visit(TypeIndex typeIndex, Void in) { return true; } }, null); } public static boolean mayBePreSmtType(Type t) { return !t.isIndex() && t.accept( new TypeVisitor() { @Override public Boolean visit(TypeVar typeVar, Void in) { return true; } @Override public Boolean visit(AlgebraicDataType algebraicType, Void in) { TypeSymbol sym = algebraicType.getSymbol(); if (sym instanceof BuiltInTypeSymbol) { switch ((BuiltInTypeSymbol) sym) { case ARRAY_TYPE: case BOOL_TYPE: case BV: case CMP_TYPE: case FP: case INT_TYPE: case LIST_TYPE: case OPTION_TYPE: case STRING_TYPE: break; case OPAQUE_SET: case MODEL_TYPE: case SMT_PATTERN_TYPE: case SMT_TYPE: case SMT_WRAPPED_VAR_TYPE: case SYM_TYPE: return false; default: throw new AssertionError("impossible"); } } for (Type typeArg : algebraicType.getTypeArgs()) { if (!typeArg.accept(this, in)) { return false; } } return true; } @Override public Boolean visit(OpaqueType opaqueType, Void in) { return false; } @Override public Boolean visit(TypeIndex typeIndex, Void in) { return true; } }, null); } public static boolean isTupleType(Type t) { if (!(t instanceof AlgebraicDataType)) { return false; } TypeSymbol sym = ((AlgebraicDataType) t).getSymbol(); return sym.equals(GlobalSymbolManager.lookupTupleTypeSymbol(sym.getArity())); } public static boolean isSmtVarType(Type t) { if (!(t instanceof AlgebraicDataType)) { return false; } return ((AlgebraicDataType) t).getSymbol().equals(BuiltInTypeSymbol.SYM_TYPE); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/types/WellTypedProgram.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.UserPredicate; public interface WellTypedProgram extends Program {} ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/unification/EmptySubstitution.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.unification; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.Collections; public enum EmptySubstitution implements Substitution { INSTANCE; @Override public void put(Var v, Term t) { throw new UnsupportedOperationException(); } @Override public Term get(Var v) { throw new UnsupportedOperationException(); } @Override public boolean containsKey(Var v) { return false; } @Override public Iterable iterateKeys() { return Collections.emptyList(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/unification/OverwriteSubstitution.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.unification; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.HashMap; import java.util.Map; public class OverwriteSubstitution implements Substitution { private final Map m; public OverwriteSubstitution() { this(new HashMap<>()); } private OverwriteSubstitution(Map m) { this.m = m; } @Override public void put(Var v, Term t) { m.put(v, t); } @Override public Term get(Var v) { assert m.containsKey(v); return m.get(v); } @Override public boolean containsKey(Var v) { return m.containsKey(v); } @Override public Iterable iterateKeys() { return m.keySet(); } public OverwriteSubstitution copy() { return new OverwriteSubstitution(new HashMap<>(m)); } @Override public String toString() { return m.toString(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/unification/SimpleSubstitution.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.unification; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.HashMap; import java.util.Map; public class SimpleSubstitution implements Substitution { private final Map map = new HashMap<>(); @Override public void put(Var v, Term t) { map.put(v, t); } @Override public Term get(Var v) { Term t = map.get(v); assert t != null; return t; } @Override public boolean containsKey(Var v) { return map.containsKey(v); } @Override public Iterable iterateKeys() { return map.keySet(); } @Override public String toString() { StringBuilder sb = new StringBuilder("["); for (Map.Entry e : map.entrySet()) { sb.append(e.getKey()); sb.append(" -> "); sb.append(e.getValue()); sb.append(" "); } sb.append("]"); return sb.toString(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/unification/Substitution.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.unification; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; public interface Substitution { void put(Var v, Term t); Term get(Var v); static Term getIfPresent(Term t, Substitution s) { if (t instanceof Var && s.containsKey((Var) t)) { return s.get((Var) t); } return t; } boolean containsKey(Var v); Iterable iterateKeys(); // Substitution copy(); // int newCheckpoint(); // int getCheckpoint(); // void revertTo(int checkpoint); // int popCheckpoint(); // void mergeCheckpoint(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/unification/Unification.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.unification; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.symbols.Symbol; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public final class Unification { private Unification() { throw new AssertionError(); } public static boolean canBindVars( ComplexLiteral atom, Set boundVars, Map varCounts) throws InvalidProgramException { return atom.accept( new ComplexLiteralExnVisitor() { @Override public Boolean visit(UserPredicate normalAtom, Void in) throws InvalidProgramException { return handleNormalAtom(normalAtom, boundVars, varCounts); } @Override public Boolean visit(UnificationPredicate unifyAtom, Void in) throws InvalidProgramException { return handleUnifyAtom(unifyAtom, boundVars); } }, null); } private static boolean handleNormalAtom( UserPredicate atom, Set boundVars, Map varCounts) { if (atom.isNegated()) { // Allow anonymous variables in negated atoms. Set vars = atom.varSet().stream() .filter(v -> !Integer.valueOf(1).equals(varCounts.get(v))) .collect(Collectors.toSet()); return boundVars.containsAll(vars); } Set nonFuncVars = new HashSet<>(); Set funcVars = new HashSet<>(); for (Term t : atom.getArgs()) { nonFuncVars.addAll(Terms.getBindingVarInstances(t)); funcVars.addAll(Terms.getNonBindingVarInstances(t)); } for (Var v : funcVars) { if (!boundVars.contains(v) && !nonFuncVars.contains(v)) { return false; } } return true; } private static boolean handleUnifyAtom(UnificationPredicate atom, Set boundVars) throws InvalidProgramException { Term t1 = atom.getLhs(); Term t2 = atom.getRhs(); if (atom.isNegated()) { return boundVars.containsAll(t1.varSet()) && boundVars.containsAll(t2.varSet()); } Set maybeBoundVars = new HashSet<>(boundVars); while (handleUnifyAtomHelper(t1, t2, maybeBoundVars)) {} return maybeBoundVars.containsAll(atom.varSet()); } private static boolean handleUnifyAtomHelper(Term t1, Term t2, Set boundVars) throws InvalidProgramException { boolean changed = false; if (Terms.isGround(t1, boundVars)) { changed |= boundVars.addAll(Terms.getBindingVarInstances(t2)); } if (Terms.isGround(t2, boundVars)) { changed |= boundVars.addAll(Terms.getBindingVarInstances(t1)); } if (t1 instanceof Constructor && t2 instanceof Constructor) { Constructor c1 = (Constructor) t1; Constructor c2 = (Constructor) t2; Symbol sym1 = c1.getSymbol(); Symbol sym2 = c2.getSymbol(); if (!sym1.equals(sym2)) { throw new InvalidProgramException( "Cannot unify a constructor of symbol " + sym1 + " with a constructor of symbol " + sym2); } Term[] args1 = c1.getArgs(); Term[] args2 = c2.getArgs(); assert args1.length == args2.length; boolean changed2; do { changed2 = false; for (int i = 0; i < args1.length; ++i) { changed2 |= handleUnifyAtomHelper(args1[i], args2[i], boundVars); changed |= changed2; } } while (changed2); } return changed; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/AbstractFJPTask.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.concurrent.RecursiveAction; @SuppressWarnings("serial") public abstract class AbstractFJPTask extends RecursiveAction { private final CountingFJP exec; protected AbstractFJPTask(CountingFJP exec) { this.exec = exec; } @Override protected void compute() { try { doTask(); exec.reportTaskCompletion(); } catch (EvaluationException e) { exec.fail(e); } } public abstract void doTask() throws EvaluationException; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/CompositeIterable.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.ArrayDeque; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; public class CompositeIterable implements Iterable { private final Iterable> its; public CompositeIterable(Iterable> its) { this.its = its; } @Override public Iterator iterator() { return new CompositeIterator(); } private final class CompositeIterator implements Iterator { private final Queue> iterators; public CompositeIterator() { Queue> l = new ArrayDeque<>(); for (Iterable it : its) { l.add(it.iterator()); } this.iterators = l; } private boolean load() { if (iterators.isEmpty()) { return false; } if (iterators.peek().hasNext()) { return true; } iterators.remove(); return load(); } @Override public synchronized boolean hasNext() { return load(); } @Override public synchronized T next() { if (!load()) { throw new NoSuchElementException(); } return iterators.peek().next(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/CountingFJP.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.eval.EvaluationException; public interface CountingFJP { void externallyAddTask(AbstractFJPTask w); void recursivelyAddTask(AbstractFJPTask w); void reportTaskCompletion(); void blockUntilFinished(); default void blockUntilFinishedExn() throws EvaluationException { blockUntilFinished(); if (hasFailed()) { throw getFailureCause(); } } void shutdown(); void fail(EvaluationException cause); boolean hasFailed(); EvaluationException getFailureCause(); long getStealCount(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/CountingFJPImpl.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class CountingFJPImpl implements CountingFJP { private final ForkJoinPool exec; private final AtomicInteger taskCount = new AtomicInteger(); private volatile EvaluationException failureCause; public CountingFJPImpl(int parallelism) { this.exec = new ForkJoinPool( parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.err.println(e); } }, false); } public void externallyAddTask(AbstractFJPTask w) { taskCount.incrementAndGet(); try { exec.execute(w); } catch (RejectedExecutionException e) { } } public void recursivelyAddTask(AbstractFJPTask w) { taskCount.incrementAndGet(); if (ForkJoinTask.inForkJoinPool()) { assert ((ForkJoinWorkerThread) Thread.currentThread()).getPool() == exec; w.fork(); } else { exec.execute(w); } } public void reportTaskCompletion() { if (taskCount.decrementAndGet() == 0) { synchronized (taskCount) { taskCount.notify(); } } } public final void blockUntilFinished() { synchronized (taskCount) { while (taskCount.get() > 0 && !hasFailed()) { try { taskCount.wait(); } catch (InterruptedException e) { // do nothing } } } } public final void shutdown() { exec.shutdown(); while (!exec.isTerminated()) { try { exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); } catch (InterruptedException e) { // do nothing } } } public final void fail(EvaluationException cause) { failureCause = cause; exec.shutdownNow(); synchronized (taskCount) { taskCount.notify(); } } public final boolean hasFailed() { return failureCause != null; } public final EvaluationException getFailureCause() { return failureCause; } @Override public long getStealCount() { return exec.getStealCount(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/Dataset.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2020-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Dataset { private final Set data = Util.concurrentSet(); public void addDataPoint(double val) { data.add(new Datum(val)); } public int size() { return data.size(); } public double computeSum() { double sum = 0; for (Datum d : data) { sum += d.val; } return sum; } public double computeMean() { assert size() > 0; return computeSum() / size(); } public double computeStdDev() { double mean = computeMean(); double varSum = 0; for (Datum d : data) { double delta = d.val - mean; varSum += delta * delta; } return Math.sqrt(varSum / (data.size() - 1)); } public List computeMinMedianMax() { List points = data.stream().sorted().collect(Collectors.toList()); int n = size(); double min = points.get(0).val; double max = points.get(n - 1).val; int mid = n / 2; double median = points.get(mid).val; if (n % 2 == 0) { median = (median + points.get(mid - 1).val) / 2; } return Arrays.asList(min, median, max); } public String getStatsString(double multiplier) { if (size() == 0) { return "-"; } List mmm = computeMinMedianMax(); return String.format( "n=%d,mean=%1.1f,min=%1.1f,median=%1.1f,max=%1.1f,stddev=%1.1f", size(), computeMean() * multiplier, mmm.get(0) * multiplier, mmm.get(1) * multiplier, mmm.get(2) * multiplier, computeStdDev() * multiplier); } public String getStatsString() { return getStatsString(1); } private static class Datum implements Comparable { public final double val; public Datum(double val) { this.val = val; } @Override public int compareTo(Datum o) { return Double.compare(val, o.val); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/DedupWorkList.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; public class DedupWorkList { private final Set seen = new HashSet<>(); private final Deque worklist = new ArrayDeque<>(); public boolean push(T item) { boolean b = seen.add(item); if (b) { worklist.add(item); } return b; } public T pop() { return worklist.pop(); } public boolean isEmpty() { return worklist.isEmpty(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/EnumerableThreadLocal.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; public class EnumerableThreadLocal { private final Set items = Util.concurrentSet(); private final ThreadLocal tl; public EnumerableThreadLocal(Supplier f) { tl = ThreadLocal.withInitial( () -> { var item = f.get(); items.add(item); return item; }); } public T get() { return tl.get(); } public void forEach(Consumer f) { items.forEach(f); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/ExceptionalFunction.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; @FunctionalInterface public interface ExceptionalFunction { T2 apply(T1 arg) throws E; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/FunctorUtil.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.symbols.Symbol; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; public final class FunctorUtil { private FunctorUtil() { throw new AssertionError(); } public static String toString(Symbol sym, Term[] args) { StringBuilder sb = new StringBuilder(sym.toString()); int arity = sym.getArity(); if (arity > 0) { sb.append('('); for (int i = 0; i < arity; ++i) { sb.append(args[i]); if (i < arity - 1) { sb.append(", "); } } sb.append(')'); } return sb.toString(); } // public static class Memoizer { // // private final Map memo = new ConcurrentHashMap<>(); // // @SuppressWarnings("unchecked") // public F lookupOrCreate(Symbol sym, Term[] args, Supplier constructor) { // if (sym.getArity() != args.length) { // throw new IllegalArgumentException("Symbol " + sym + " has arity " + // sym.getArity() + " but args " // + Arrays.toString(args) + " have length " + args.length); // } // Map m = memo; // Object k = sym; // for (int i = 0; i < args.length; ++i) { // m = (Map) Util.lookupOrCreate(m, k, () -> new // ConcurrentHashMap<>()); // k = args[i]; // } // return (F) Util.lookupOrCreate(m, k, () -> constructor.get()); // } // // } public static class Memoizer { private final Map memo = new ConcurrentHashMap<>(); public T lookupOrCreate(Symbol sym, Term[] args, Supplier constructor) { if (sym.getArity() != args.length) { throw new IllegalArgumentException( "Symbol " + sym + " has arity " + sym.getArity() + " but args " + Arrays.toString(args) + " have arity " + args.length); } Key key = new Key(sym, args); T f = memo.get(key); if (f == null) { f = constructor.get(); T f2 = memo.putIfAbsent(key, f); if (f2 != null) { f = f2; } } return f; } private static class Key { private final Symbol sym; private final Term[] args; private final int hashCode; public Key(Symbol sym, Term[] args) { this.sym = sym; this.args = args; final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(args); result = prime * result + ((sym == null) ? 0 : sym.hashCode()); hashCode = result; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Key other = (Key) obj; if (hashCode != other.hashCode) { return false; } if (sym == null) { if (other.sym != null) return false; } else if (!sym.equals(other.sym)) return false; if (!Arrays.equals(args, other.args)) return false; return true; } } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/IntArrayWrapper.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.Arrays; public class IntArrayWrapper { private final int[] a; public IntArrayWrapper(int[] a) { this.a = a; } public int[] getVal() { return a; } @Override public int hashCode() { return Arrays.hashCode(a); } @Override public boolean equals(Object o) { if (o instanceof int[]) { return Arrays.equals(a, (int[]) o); } return false; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/MockCountingFJP.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import java.util.ArrayDeque; import java.util.Deque; public class MockCountingFJP implements CountingFJP { private volatile EvaluationException failureCause; private final Deque workItems = new ArrayDeque<>(); public synchronized void externallyAddTask(AbstractFJPTask w) { workItems.addLast(w); } public synchronized void recursivelyAddTask(AbstractFJPTask w) { workItems.addLast(w); } public final synchronized void blockUntilFinished() { while (!workItems.isEmpty() && !hasFailed()) { AbstractFJPTask task = workItems.removeLast(); task.compute(); } } public final synchronized void shutdown() {} public final synchronized void fail(EvaluationException cause) { failureCause = cause; } public final synchronized boolean hasFailed() { return failureCause != null; } public final synchronized EvaluationException getFailureCause() { return failureCause; } @Override public synchronized void reportTaskCompletion() {} @Override public long getStealCount() { return 0; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/Pair.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.Set; import java.util.function.BiFunction; import java.util.stream.Collectors; public class Pair { private final U fst; private final V snd; public Pair(U fst, V snd) { this.fst = fst; this.snd = snd; } public U fst() { return fst; } public V snd() { return snd; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((fst == null) ? 0 : fst.hashCode()); result = prime * result + ((snd == null) ? 0 : snd.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("rawtypes") Pair other = (Pair) obj; if (fst == null) { if (other.fst != null) return false; } else if (!fst.equals(other.fst)) return false; if (snd == null) { if (other.snd != null) return false; } else if (!snd.equals(other.snd)) return false; return true; } @Override public String toString() { return "<" + fst + ", " + snd + ">"; } public static Set map(Set> s, BiFunction f) { return s.stream().map(p -> f.apply(p.fst(), p.snd())).collect(Collectors.toSet()); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/SharedLong.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.Set; public class SharedLong { private static class Box { long val; } private final Set boxes = Util.concurrentSet(); private final ThreadLocal tl = ThreadLocal.withInitial( () -> { var box = new Box(); boxes.add(box); return box; }); public void increment() { tl.get().val++; } public void add(long delta) { if (delta == 0) { return; } tl.get().val += delta; } public long unsafeGet() { long sum = 0; for (var box : boxes) { sum += box.val; } return sum; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/StackMap.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.Map; public class StackMap { private final Deque> stack = new ArrayDeque<>(); public synchronized void push(Map m) { stack.addFirst(m); } public synchronized Map pop() { return stack.remove(); } public synchronized V get(K k) { for (Iterator> it = stack.iterator(); it.hasNext(); ) { Map m = it.next(); V v = m.get(k); if (v != null) { return v; } } return null; } public synchronized V put(K k, V v) { return stack.getFirst().put(k, v); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/TodoException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; public class TodoException extends RuntimeException { private static final long serialVersionUID = -3552412612033483231L; public TodoException() {} public TodoException(String message) { super(message); } public TodoException(Throwable cause) { super(cause); } public TodoException(String message, Throwable cause) { super(message, cause); } public TodoException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/Triple.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; public class Triple { public final S first; public final T second; public final U third; public Triple(S first, T second, U third) { this.first = first; this.second = second; this.third = third; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((second == null) ? 0 : second.hashCode()); result = prime * result + ((third == null) ? 0 : third.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("rawtypes") Triple other = (Triple) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (second == null) { if (other.second != null) return false; } else if (!second.equals(other.second)) return false; if (third == null) { if (other.third != null) return false; } else if (!third.equals(other.third)) return false; return true; } @Override public String toString() { return "< " + first + " , " + second + " , " + third + " >"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/UnionFind.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import java.util.HashMap; import java.util.Map; import java.util.Set; public class UnionFind { private final Map m = new HashMap<>(); public boolean contains(T t) { return m.containsKey(t); } public boolean add(T t) { return m.putIfAbsent(t, t) == null; } public void union(T t1, T t2) { t1 = find(t1); t2 = find(t2); m.put(t1, t2); } public T find(T t) { assert contains(t); T prev = null; while (!t.equals(prev)) { prev = t; t = m.get(prev); } return t; } public Set members() { return m.keySet(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/Util.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import org.jgrapht.Graph; public final class Util { private Util() { throw new AssertionError(); } public static V lookupOrCreate(Map m, K k, Supplier cnstr) { V v = m.get(k); if (v == null) { v = cnstr.get(); V u = m.putIfAbsent(k, v); if (u != null) { v = u; } } return v; } public static Iterable i2i(Iterator it) { return () -> it; } public static Set concurrentSet() { return Collections.newSetFromMap(new ConcurrentHashMap<>()); } public static List iterableToList(Iterable it) { List l = new ArrayList<>(); it.forEach(l::add); return l; } public static List map(List xs, Function f) { return xs.stream().map(f).collect(Collectors.toList()); } public static void printSortedFacts(Iterable facts, PrintStream out) { Util.iterableToList(facts).stream() .map( a -> { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); ps.print(a); return baos.toString(); }) .sorted() .forEach(out::println); } public static Map fillMapWithFutures(Map> futures, Map m) throws InterruptedException, ExecutionException { for (Map.Entry> e : futures.entrySet()) { m.put(e.getKey(), e.getValue().get()); } return m; } public static class IterableOfIterables implements Iterable> { private final Iterable iterable; private final int size; public IterableOfIterables(Iterable iterable, int size) { this.iterable = iterable; this.size = size; } @Override public Iterator> iterator() { Iterator it = iterable.iterator(); return new Iterator>() { @Override public boolean hasNext() { return it.hasNext(); } @Override public Iterable next() { assert hasNext(); List l = new ArrayList<>(size); for (int i = 0; i < size && it.hasNext(); ++i) { l.add(it.next()); } return l; } }; } @Override public String toString() { String s = "{"; for (Iterator> it = iterator(); it.hasNext(); ) { s += it.next(); if (it.hasNext()) { s += ","; } } return s + "}"; } public String toString(Function printer) { String s = "{"; for (Iterator> it = iterator(); it.hasNext(); ) { s += " {"; for (Iterator it2 = it.next().iterator(); it2.hasNext(); ) { s += printer.apply(it2.next()); if (it2.hasNext()) { s += ", "; } } s += "} "; if (it.hasNext()) { s += ", "; } } return s + "}"; } } public static Iterable> splitIterable(Iterable iterable, int segmentSize) { return new IterableOfIterables<>(iterable, segmentSize); } public static void clean(File f, boolean deleteTopLevel) { if (!f.exists()) { return; } if (f.isDirectory()) { for (File ff : f.listFiles()) { clean(ff, true); } } if (deleteTopLevel) { f.delete(); } } public static void assertBinaryOnPath(String exec) { String os = System.getProperty("os.name"); String util = os.startsWith("Windows") ? "where" : "which"; String[] cmd = {util, exec}; String strCmd = util + " " + exec; try { Process p = Runtime.getRuntime().exec(cmd); if (p.waitFor() != 0) { throw new AssertionError( "Cannot find " + exec + " executable on path (`" + strCmd + "` returned a non-zero exit code)."); } } catch (IOException | InterruptedException e) { throw new AssertionError( "Command checking for presence of " + exec + " executable failed: " + strCmd + "\n" + e.getMessage()); } } public static void printGraph(PrintStream out, Graph g) { out.println("{"); for (Iterator it = g.vertexSet().iterator(); it.hasNext(); ) { V v = it.next(); out.println("\t" + v + ":"); if (g.outDegreeOf(v) == 0) { out.println("\t\tNONE"); } else { for (E e : g.outgoingEdgesOf(v)) { out.println("\t\t" + e); } } if (it.hasNext()) { out.println(); } } out.println("}"); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExp.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; import java.util.List; public interface SExp { boolean isAtom(); default boolean isList() { return !isAtom(); } String asAtom(); List asList(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpAtom.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; import java.util.List; public class SExpAtom implements SExp { private final String value; public SExpAtom(String value) { this.value = value; } @Override public boolean isAtom() { return true; } @Override public String asAtom() { return value; } @Override public List asList() { throw new UnsupportedOperationException(); } @Override public String toString() { return value; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SExpAtom other = (SExpAtom) obj; if (value == null) { if (other.value != null) return false; } else if (!value.equals(other.value)) return false; return true; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; public class SExpException extends Exception { private static final long serialVersionUID = -8286351759750678592L; public SExpException() {} public SExpException(String message) { super(message); } public SExpException(Throwable cause) { super(cause); } public SExpException(String message, Throwable cause) { super(message, cause); } public SExpException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpLexer.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2021-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; import java.io.Reader; import java.io.StringReader; public class SExpLexer { private static final int BUF_CAPACITY = 1024; private char[] buf = new char[BUF_CAPACITY]; private int pos = 0; private int bufSize = 0; private boolean ready; private SExpToken curToken; private String curValue; private final Reader reader; public SExpLexer(Reader reader) { this.reader = reader; } public SExpToken curToken() throws SExpException { if (!ready && !loadNextToken()) { throw new SExpException("No more tokens"); } return curToken; } public String curValue() throws SExpException { if (!ready && !loadNextToken()) { throw new SExpException("No more tokens"); } if (curValue == null) { throw new SExpException("No value associated with current token"); } return curValue; } public void step() throws SExpException { if (!ready) { loadNextToken(); } ready = false; } public boolean hasToken() throws SExpException { return ready || loadNextToken(); } public void consume(SExpToken token) throws SExpException { SExpToken got = curToken(); if (got == token) { step(); } else { throw new SExpException("Expected " + token + " but got " + got); } } private boolean loadNextToken() throws SExpException { curToken = null; curValue = null; while (load() && Character.isWhitespace(buf[pos])) { pos++; } if (!load()) { return false; } char ch = buf[pos]; switch (ch) { case '(': curToken = SExpToken.LPAREN; pos++; break; case ')': curToken = SExpToken.RPAREN; pos++; break; default: curToken = SExpToken.ATOM; curValue = readAtom(); } ready = true; return true; } private String readAtom() throws SExpException { StringBuilder sb = new StringBuilder(); char ch; while (load() && (ch = buf[pos]) != ')' && ch != '(' && !Character.isWhitespace(ch)) { sb.append(ch); pos++; } return sb.toString(); } private boolean load() throws SExpException { if (pos >= bufSize) { try { bufSize = reader.read(buf); } catch (Exception e) { throw new SExpException(e); } pos = 0; } return bufSize > 0; } public static void main(String[] args) throws SExpException { Reader r = new StringReader("(hello 2(x ()) )"); SExpLexer lexer = new SExpLexer(r); while (lexer.hasToken()) { switch (lexer.curToken) { case ATOM: System.out.println(lexer.curValue()); break; case LPAREN: System.out.println("("); break; case RPAREN: System.out.println(")"); break; } lexer.step(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpList.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; public class SExpList implements SExp { private final List l; public SExpList(List l) { this.l = Collections.unmodifiableList(new ArrayList<>(l)); } @Override public boolean isAtom() { return false; } @Override public String asAtom() { throw new UnsupportedOperationException(); } @Override public List asList() { return l; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("("); for (Iterator it = l.iterator(); it.hasNext(); ) { sb.append(it.next()); if (it.hasNext()) { sb.append(" "); } } sb.append(")"); return sb.toString(); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpParser.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2021-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; public class SExpParser { private final SExpLexer lexer; public SExpParser(Reader r) { lexer = new SExpLexer(r); } public SExp parse() throws SExpException { switch (lexer.curToken()) { case ATOM: SExp atom = new SExpAtom(lexer.curValue()); lexer.step(); return atom; case LPAREN: return parseList(); case RPAREN: throw new SExpException("Unexpected right parenthesis"); default: throw new AssertionError("Impossible"); } } private SExp parseList() throws SExpException { lexer.consume(SExpToken.LPAREN); List l = new ArrayList<>(); boolean go = true; while (go) { switch (lexer.curToken()) { case ATOM: l.add(new SExpAtom(lexer.curValue())); lexer.step(); break; case LPAREN: l.add(parseList()); break; case RPAREN: go = false; break; } } lexer.consume(SExpToken.RPAREN); return new SExpList(l); } public static void main(String[] args) throws SExpException { Reader r = new StringReader("(hello 2(x ()) )"); SExpParser parser = new SExpParser(r); System.out.println(parser.parse()); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/util/sexp/SExpToken.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.util.sexp; public enum SExpToken { LPAREN, RPAREN, ATOM; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/FunctionDefValidation.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; public class FunctionDefValidation { private FunctionDefValidation() { throw new AssertionError(); } public static void validate(Program prog) throws InvalidProgramException { for (FunctionSymbol sym : prog.getFunctionSymbols()) { FunctionDef def = prog.getDef(sym); if (def instanceof UserFunctionDef) { try { validate((UserFunctionDef) def); } catch (InvalidProgramException e) { throw new InvalidProgramException( "Invalid function definition: " + sym + "\n" + e.getMessage()); } } } } public static void validate(UserFunctionDef def) throws InvalidProgramException { Set params = checkParams(def.getParams()); Set vars = def.getBody().varSet(); vars.removeAll(params); if (!vars.isEmpty()) { String msg = "Unbound variable(s): "; for (Iterator it = vars.iterator(); it.hasNext(); ) { msg += it.next(); if (it.hasNext()) { msg += ", "; } } throw new InvalidProgramException(msg); } } private static Set checkParams(List params) throws InvalidProgramException { Set vars = new HashSet<>(); for (Var param : params) { if (!vars.add(param)) { throw new InvalidProgramException("Repeated parameter: " + param); } } return vars; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/InvalidProgramException.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; public class InvalidProgramException extends Exception { private static final long serialVersionUID = 5958219607857975431L; /** Constructs an exception signifying an ill-formedness error. */ public InvalidProgramException() {} /** * Constructs an exception signifying an ill-formedness error. * * @param message the error message */ public InvalidProgramException(String message) { super(message); } /** * Constructs an exception signifying an ill-formedness error. * * @param cause the exception that caused this exception */ public InvalidProgramException(Throwable cause) { super(cause); } /** * Constructs an exception signifying an ill-formedness error. * * @param message the error message * @param cause the exception that caused this exception */ public InvalidProgramException(String message, Throwable cause) { super(message, cause); } /** * Constructs an exception signifying an ill-formedness error. * * @param message the error message * @param cause the exception that caused this exception * @param enableSuppression whether or not suppression is enabled or disabled * @param writableStackTrace whether or not the stack trace should be writable */ public InvalidProgramException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/Stratifier.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.BasicRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralVisitor; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Expr; import edu.harvard.seas.pl.formulog.ast.Exprs.ExprVisitor; import edu.harvard.seas.pl.formulog.ast.Fold; import edu.harvard.seas.pl.formulog.ast.FunctionCallFactory.FunctionCall; import edu.harvard.seas.pl.formulog.ast.LetFunExpr; import edu.harvard.seas.pl.formulog.ast.MatchClause; import edu.harvard.seas.pl.formulog.ast.MatchExpr; import edu.harvard.seas.pl.formulog.ast.Primitive; import edu.harvard.seas.pl.formulog.ast.Program; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Terms.TermVisitor; import edu.harvard.seas.pl.formulog.ast.UnificationPredicate; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.functions.FunctionDef; import edu.harvard.seas.pl.formulog.functions.UserFunctionDef; import edu.harvard.seas.pl.formulog.symbols.BuiltInFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.FunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.PredicateFunctionSymbol; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.KosarajuStrongConnectivityInspector; import org.jgrapht.alg.interfaces.StrongConnectivityAlgorithm; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.traverse.TopologicalOrderIterator; public class Stratifier { private final Program prog; public Stratifier(Program prog) { this.prog = prog; } public List stratify() throws InvalidProgramException { Graph g = new DefaultDirectedGraph<>(DependencyTypeWrapper.class); for (RelationSymbol sym : prog.getRuleSymbols()) { DependencyFinder depends = new DependencyFinder(); for (BasicRule r : prog.getRules(sym)) { for (ComplexLiteral bd : r) { depends.processAtom(bd); } UserPredicate hd = r.getHead(); for (Term t : hd.getArgs()) { depends.processTerm(t); } } g.addVertex(sym); for (RelationSymbol bdSym : depends) { g.addVertex(bdSym); g.addEdge(bdSym, sym, new DependencyTypeWrapper(depends.getDependencyType(bdSym))); } } StrongConnectivityAlgorithm k = new KosarajuStrongConnectivityInspector<>(g); Graph, DefaultEdge> condensation = k.getCondensation(); TopologicalOrderIterator, DefaultEdge> topo = new TopologicalOrderIterator<>(condensation); List strata = new ArrayList<>(); int rank = 0; InvalidProgramException exn = null; while (topo.hasNext()) { boolean hasRecursiveNegationOrAggregation = false; Graph component = topo.next(); Set edbs = component.vertexSet().stream().filter(r -> r.isEdbSymbol()).collect(Collectors.toSet()); if (!edbs.isEmpty()) { if (!component.edgeSet().isEmpty()) { throw new InvalidProgramException("EDB relations cannot have dependencies: " + edbs); } continue; } for (DependencyTypeWrapper dw : component.edgeSet()) { DependencyType d = dw.get(); if (d.equals(DependencyType.NEG_OR_AGG_IN_FUN) && exn == null) { exn = new InvalidProgramException( "Not stratified: the relation " + component.getEdgeSource(dw) + " is treated as an aggregate expression in the definition of relation " + component.getEdgeTarget(dw)); } hasRecursiveNegationOrAggregation |= d.equals(DependencyType.NEG_OR_AGG_IN_REL); } strata.add(new Stratum(rank, component.vertexSet(), hasRecursiveNegationOrAggregation)); if (Configuration.debugStratification) { toDot(rank, component); } rank++; } if (exn != null) { throw exn; } return strata; } private static void toDot( int stratumNumber, Graph component) { try { Path base = Files.createDirectories(Paths.get(Configuration.debugStratificationOutDir)); String name = "stratum_" + stratumNumber; File out = base.resolve(name + ".dot").toFile(); try (PrintWriter pw = new PrintWriter(out)) { pw.println("digraph " + name + " {"); for (RelationSymbol sym : component.vertexSet()) { pw.println("\t" + sym + ";"); } for (DependencyTypeWrapper e : component.edgeSet()) { pw.print("\t"); pw.print(component.getEdgeSource(e)); pw.print(" -> "); pw.print(component.getEdgeTarget(e)); pw.println(" [label=" + e + "];"); } pw.println("}"); } } catch (IOException e) { System.err.println("Error while writing stratification debug info"); } } private class DependencyFinder implements Iterable { private final Set visitedFunctions = new HashSet<>(); private final Set allDependencies = new HashSet<>(); private final Set negOrAggFunDependencies = new HashSet<>(); private final Set negOrAggRelDependencies = new HashSet<>(); // private boolean isAggregate(Atom a) { // Symbol sym = a.getSymbol(); // RelationProperties props = prog.getRelationProperties(sym); // return props != null && props.isAggregated(); // } public void processAtom(ComplexLiteral a) { a.accept( new ComplexLiteralVisitor() { @Override public Void visit(UnificationPredicate unificationPredicate, Void input) { processTerm(unificationPredicate.getLhs()); processTerm(unificationPredicate.getRhs()); return null; } @Override public Void visit(UserPredicate userPredicate, Void input) { if (userPredicate.isNegated()) { addNegOrAggRel(userPredicate.getSymbol()); } else { addPositive(userPredicate.getSymbol()); } for (Term t : userPredicate.getArgs()) { processTerm(t); } return null; } }, null); } public DependencyType getDependencyType(RelationSymbol sym) { // Order is important here, since having a negative or aggregate // dependency within a function body subsumes having one in a // relation definition. if (negOrAggFunDependencies.contains(sym)) { return DependencyType.NEG_OR_AGG_IN_FUN; } if (negOrAggRelDependencies.contains(sym)) { return DependencyType.NEG_OR_AGG_IN_REL; } return DependencyType.POSITIVE; } private void addNegOrAggFun(RelationSymbol sym) { negOrAggFunDependencies.add(sym); allDependencies.add(sym); } private void addNegOrAggRel(RelationSymbol sym) { negOrAggRelDependencies.add(sym); allDependencies.add(sym); } private void addPositive(RelationSymbol sym) { allDependencies.add(sym); } public void processTerm(Term t) { t.accept( new TermVisitor() { @Override public Void visit(Var t, Void in) { return null; } @Override public Void visit(Constructor t, Void in) { for (Term arg : t.getArgs()) { arg.accept(this, null); } return null; } @Override public Void visit(Primitive prim, Void in) { return null; } @Override public Void visit(Expr expr, Void in) { processExpr(expr); return null; } }, null); } private void processExpr(Expr expr) { expr.accept( new ExprVisitor() { @Override public Void visit(MatchExpr matchExpr, Void in) { processTerm(matchExpr.getMatchee()); for (MatchClause cl : matchExpr) { processTerm(cl.getLhs()); processTerm(cl.getRhs()); } return null; } @Override public Void visit(FunctionCall funcCall, Void in) { processFunctionSymbol(funcCall.getSymbol()); for (Term arg : funcCall.getArgs()) { processTerm(arg); } return null; } @Override public Void visit(LetFunExpr funcDef, Void in) { throw new AssertionError("impossible"); } @Override public Void visit(Fold fold, Void in) { fold.getShamCall().accept(this, in); return null; } }, null); } private void processFunctionSymbol(FunctionSymbol sym) { if (!visitedFunctions.add(sym) || sym instanceof BuiltInFunctionSymbol) { return; } if (sym instanceof PredicateFunctionSymbol) { addNegOrAggFun(((PredicateFunctionSymbol) sym).getPredicateSymbol()); return; } FunctionDef def1 = prog.getDef(sym); if (def1 instanceof UserFunctionDef) { UserFunctionDef def = (UserFunctionDef) def1; processTerm(def.getBody()); } } @Override public Iterator iterator() { return allDependencies.iterator(); } } private static enum DependencyType { NEG_OR_AGG_IN_FUN, NEG_OR_AGG_IN_REL, POSITIVE; } // Needed because edges need to have unique objects as labels... private static class DependencyTypeWrapper { private final DependencyType d; public DependencyTypeWrapper(DependencyType d) { this.d = d; } public DependencyType get() { return d; } @Override public String toString() { return d.toString(); } } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/Stratum.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import java.util.Set; public class Stratum { private final int rank; private final Set predicateSyms; private final boolean hasRecursiveNegationOrAggregation; public Stratum( int rank, Set predicateSyms, boolean hasRecursiveNegationOrAggregation) { this.rank = rank; this.predicateSyms = predicateSyms; this.hasRecursiveNegationOrAggregation = hasRecursiveNegationOrAggregation; } public int getRank() { return rank; } public Set getPredicateSyms() { return predicateSyms; } public boolean hasRecursiveNegationOrAggregation() { return hasRecursiveNegationOrAggregation; } @Override public String toString() { return "Stratum [rank=" + rank + ", predicateSyms=" + predicateSyms + ", hasRecursiveNegationOrAggregation=" + hasRecursiveNegationOrAggregation + "]"; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ValidRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import edu.harvard.seas.pl.formulog.ast.AbstractRule; import edu.harvard.seas.pl.formulog.ast.ComplexLiteral; import edu.harvard.seas.pl.formulog.ast.Rule; import edu.harvard.seas.pl.formulog.ast.UserPredicate; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.unification.Unification; import edu.harvard.seas.pl.formulog.util.Util; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; public class ValidRule extends AbstractRule { public static ValidRule make( Rule rule, BiFunction, Integer> score) throws InvalidProgramException { try { List body = Util.iterableToList(rule); Set vars = new HashSet<>(); // Reordering is currently unsound... also need to type check. order(body, score, vars, rule.countVariables()); // Set vars = checkBody(rule); UserPredicate head = rule.getHead(); if (!head.getSymbol().isIdbSymbol()) { throw new InvalidProgramException( "Cannot create a rule for non-IDB symbol " + head.getSymbol()); } if (!vars.containsAll(head.varSet())) { String msg = "There are unbound variables in the head of a rule:"; for (Var x : head.varSet()) { if (!vars.contains(x)) { msg += " " + x; } } throw new InvalidProgramException(msg); } return new ValidRule(head, body); } catch (InvalidProgramException e) { throw new InvalidProgramException(e.getMessage() + "\n" + rule); } } public static void order( List atoms, BiFunction, Integer> score, Set boundVars, Map varCounts) throws InvalidProgramException { List newList = new ArrayList<>(); // Using a linked hash set ensures the sort is stable. Set unplaced = new LinkedHashSet<>(atoms); while (!unplaced.isEmpty()) { ComplexLiteral bestLit = null; int bestScore = -1; for (ComplexLiteral l : unplaced) { if (Unification.canBindVars(l, boundVars, varCounts)) { int localScore = score.apply(l, boundVars); if (localScore > bestScore) { bestScore = localScore; bestLit = l; } } } if (bestLit == null) { throw new InvalidProgramException("Literals do not admit an evaluable reordering"); } newList.add(bestLit); boundVars.addAll(bestLit.varSet()); unplaced.remove(bestLit); } atoms.clear(); atoms.addAll(newList); } // private static Set checkBody(Rule rule) // throws InvalidProgramException { // Set boundVars = new HashSet<>(); // Map varCounts = rule.countVariables(); // for (ComplexLiteral lit : rule) { // if (!Unification.canBindVars(lit, boundVars, varCounts)) { // throw new InvalidProgramException( // "Rule cannot be evaluated given the supplied order.\n" // + "The problematic rule is:\n" // + rule // + "\nThe problematic literal is: " // + lit); // } // boundVars.addAll(lit.varSet()); // } // return boundVars; // } private ValidRule(UserPredicate head, List body) { super(head, body); } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/Assignment.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Set; public class Assignment implements SimpleLiteral { private final Var var; private final Term rhs; public static Assignment make(Var var, Term rhs) { return new Assignment(var, rhs); } public Assignment(Var var, Term rhs) { this.var = var; this.rhs = rhs; } public void assign(Substitution subst) throws EvaluationException { subst.put(var, rhs.normalize(subst)); } @Override public O accept(SimpleLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(SimpleLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } @Override public String toString() { return var + " <- " + rhs; } @Override public void varSet(Set vars) { vars.add(var); rhs.varSet(vars); } @Override public SimpleLiteralTag getTag() { return SimpleLiteralTag.ASSIGNMENT; } @Override public Term[] getArgs() { return new Term[] {var, rhs}; } public Var getDef() { return var; } public Term getVal() { return rhs; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/Check.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Set; public class Check implements SimpleLiteral { private final Term lhs; private final Term rhs; private final boolean negated; public static Check make(Term lhs, Term rhs, boolean negated) { return new Check(lhs, rhs, negated); } private Check(Term lhs, Term rhs, boolean negated) { this.lhs = lhs; this.rhs = rhs; this.negated = negated; } public boolean isNegated() { return negated; } public Term getLhs() { return lhs; } public Term getRhs() { return rhs; } public boolean check(Substitution subst) throws EvaluationException { Term lhs = this.lhs.normalize(subst); Term rhs = this.rhs.normalize(subst); assert lhs.isGround(); assert !lhs.containsUnevaluatedTerm(); assert rhs.isGround(); assert !rhs.containsUnevaluatedTerm(); return lhs.equals(rhs) ^ negated; } @Override public O accept(SimpleLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(SimpleLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } @Override public String toString() { return lhs + (negated ? " != " : " = ") + rhs; } @Override public void varSet(Set vars) { lhs.varSet(vars); rhs.varSet(vars); } @Override public SimpleLiteralTag getTag() { return SimpleLiteralTag.CHECK; } @Override public Term[] getArgs() { return new Term[] {lhs, rhs}; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/Destructor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.ast.Constructor; import edu.harvard.seas.pl.formulog.ast.Constructors; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.ConstructorSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; public class Destructor implements SimpleLiteral { private final Term x; private final ConstructorSymbol symbol; private final Var[] bindings; public static Destructor make(Term x, ConstructorSymbol symbol, Var[] bindings) { assert assertDisjoint(x.varSet(), Arrays.asList(bindings)) : "A variable cannot be on both sides of a destructor"; assert symbol.getArity() == bindings.length : "Symbol arity does not match number of variables"; assert bindings.length == new HashSet<>(Arrays.asList(bindings)).size() : "All variables must be distinct"; return new Destructor(x, symbol, bindings); } private static boolean assertDisjoint(Collection xs, Collection ys) { for (Var x : xs) { if (ys.contains(x)) { return false; } } return true; } private Destructor(Term x, ConstructorSymbol symbol, Var[] vars) { this.x = x; this.symbol = symbol; this.bindings = vars; } public Term getScrutinee() { return x; } public ConstructorSymbol getSymbol() { return symbol; } public Var[] getBindings() { return bindings; } public boolean destruct(Substitution subst) throws EvaluationException { var scrutinee = x.normalize(subst); if (!(scrutinee instanceof Constructor)) { return false; } Constructor ctor = (Constructor) scrutinee; if (!ctor.getSymbol().equals(symbol)) { return false; } Term[] args = ctor.getArgs(); for (int i = 0; i < args.length; ++i) { subst.put(bindings[i], args[i]); } return true; } @Override public O accept(SimpleLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(SimpleLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } @Override public String toString() { String s = x + " -> " + symbol + "("; for (int i = 0; i < bindings.length; ++i) { s += bindings[i]; if (i < bindings.length - 1) { s += ", "; } } s += ")"; return s; } @Override public void varSet(Set vars) { x.varSet(vars); vars.addAll(Arrays.asList(bindings)); } @Override public SimpleLiteralTag getTag() { return SimpleLiteralTag.DESTRUCTOR; } @Override public Term[] getArgs() { Term[] args = new Term[bindings.length]; for (int i = 0; i < args.length; ++i) { args[i] = bindings[i]; } return new Term[] {x, Constructors.make(symbol, args)}; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimpleLiteral.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.ast.Literal; import edu.harvard.seas.pl.formulog.ast.Var; import java.util.HashSet; import java.util.Set; public interface SimpleLiteral extends Literal { O accept(SimpleLiteralVisitor visitor, I input); O accept(SimpleLiteralExnVisitor visitor, I input) throws E; default Set varSet() { Set vars = new HashSet<>(); varSet(vars); return vars; } void varSet(Set vars); SimpleLiteralTag getTag(); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimpleLiteralExnVisitor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; public interface SimpleLiteralExnVisitor { O visit(Assignment assignment, I input) throws E; O visit(Check check, I input) throws E; O visit(Destructor destructor, I input) throws E; O visit(SimplePredicate predicate, I input) throws E; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimpleLiteralTag.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; public enum SimpleLiteralTag { ASSIGNMENT, CHECK, DESTRUCTOR, PREDICATE; } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimpleLiteralVisitor.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; public interface SimpleLiteralVisitor { O visit(Assignment assignment, I input); O visit(Check check, I input); O visit(Destructor destructor, I input); O visit(SimplePredicate predicate, I input); } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimplePredicate.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.ast.BindingType; import edu.harvard.seas.pl.formulog.ast.Term; import edu.harvard.seas.pl.formulog.ast.Var; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.unification.Substitution; import java.util.Arrays; import java.util.Set; public class SimplePredicate implements SimpleLiteral { private final RelationSymbol symbol; private final Term[] args; private final BindingType[] bindingPattern; private final boolean negated; public static SimplePredicate make( RelationSymbol symbol, Term[] args, BindingType[] bindingPattern, boolean negated) { assert symbol.getArity() == args.length : "Symbol does not match argument arity"; return new SimplePredicate(symbol, args, bindingPattern, negated); } private SimplePredicate( RelationSymbol symbol, Term[] args, BindingType[] bindingPattern, boolean negated) { this.symbol = symbol; this.args = args; this.bindingPattern = bindingPattern; this.negated = negated; } public RelationSymbol getSymbol() { return symbol; } @Override public Term[] getArgs() { return args; } public BindingType[] getBindingPattern() { return bindingPattern; } public boolean isNegated() { return negated; } @Override public O accept(SimpleLiteralVisitor visitor, I input) { return visitor.visit(this, input); } @Override public O accept(SimpleLiteralExnVisitor visitor, I input) throws E { return visitor.visit(this, input); } @Override public String toString() { String s = ""; if (negated) { s += "!"; } s += symbol; if (args.length > 0) { s += "("; for (int i = 0; i < args.length; ++i) { s += args[i] + " : "; switch (bindingPattern[i]) { case BOUND: s += "b"; break; case FREE: s += "f"; break; case IGNORED: s += "i"; break; } if (i < args.length - 1) { s += ", "; } } s += ")"; } return s; } public SimplePredicate normalize(Substitution s) throws EvaluationException { Term[] newArgs = new Term[args.length]; for (int i = 0; i < args.length; ++i) { newArgs[i] = args[i].normalize(s); } return new SimplePredicate(symbol, newArgs, bindingPattern, negated); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(args); result = prime * result + Arrays.hashCode(bindingPattern); result = prime * result + (negated ? 1231 : 1237); result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SimplePredicate other = (SimplePredicate) obj; if (!Arrays.equals(args, other.args)) return false; if (!Arrays.equals(bindingPattern, other.bindingPattern)) return false; if (negated != other.negated) return false; if (symbol == null) { if (other.symbol != null) return false; } else if (!symbol.equals(other.symbol)) return false; return true; } @Override public void varSet(Set vars) { for (Term arg : args) { arg.varSet(vars); } } @Override public SimpleLiteralTag getTag() { return SimpleLiteralTag.PREDICATE; } } ================================================ FILE: src/main/java/edu/harvard/seas/pl/formulog/validating/ast/SimpleRule.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating.ast; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.ast.*; import edu.harvard.seas.pl.formulog.ast.ComplexLiterals.ComplexLiteralExnVisitor; import edu.harvard.seas.pl.formulog.unification.SimpleSubstitution; import edu.harvard.seas.pl.formulog.unification.Substitution; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import edu.harvard.seas.pl.formulog.validating.ValidRule; import java.util.*; public class SimpleRule extends AbstractRule { public static SimpleRule make(ValidRule rule, FunctionCallFactory funcFactory) throws InvalidProgramException { Map varCounts = rule.countVariables(); Simplifier simplifier = new Simplifier(varCounts); for (ComplexLiteral atom : rule) { try { simplifier.add(atom); } catch (InvalidProgramException e) { throw new InvalidProgramException( "Problem simplifying this rule:\n" + rule + "\nCould not simplify this atom: " + atom + "\nReason:\n" + e.getMessage()); } } UserPredicate head = rule.getHead().applySubstitution(simplifier.getSubst()); Set boundVars = simplifier.getBoundVars(); if (!boundVars.containsAll(head.varSet())) { throw new InvalidProgramException("Unbound variables in head of rule:\n" + rule); } Term[] headArgs = head.getArgs(); BindingType[] pat = computeBindingPattern(headArgs, boundVars, varCounts); SimplePredicate newHead = SimplePredicate.make(head.getSymbol(), head.getArgs(), pat, head.isNegated()); return new SimpleRule(newHead, simplifier.getConjuncts()); } // XXX This isn't great because it doesn't check to make sure the invariants of // a SimpleRule are actually maintained. // public static SimpleRule make(SimplePredicate head, List body) { // return new SimpleRule(head, body); // } private SimpleRule(SimplePredicate head, List body) { super(head, body); } private static BindingType[] computeBindingPattern( Term[] args, Set boundVars, Map counts) { BindingType[] pat = new BindingType[args.length]; for (int i = 0; i < pat.length; ++i) { Term arg = args[i]; if (arg instanceof Var && Integer.valueOf(1).equals(counts.get(arg))) { pat[i] = BindingType.IGNORED; } else if (boundVars.containsAll(arg.varSet())) { pat[i] = BindingType.BOUND; } else { pat[i] = BindingType.FREE; } } return pat; } private static class Simplifier { private final List acc = new ArrayList<>(); private final Set boundVars = new HashSet<>(); private final Map varCounts; private final Substitution subst = new SimpleSubstitution(); public Simplifier(Map varCounts) { this.varCounts = varCounts; } public Substitution getSubst() { return subst; } public void add(ComplexLiteral atom) throws InvalidProgramException { List todo = new ArrayList<>(); atom = atom.applySubstitution(subst); SimpleLiteral c = atom.accept( new ComplexLiteralExnVisitor() { @Override public SimpleLiteral visit(UnificationPredicate unificationPredicate, Void input) throws InvalidProgramException { Term lhs = unificationPredicate.getLhs(); Term rhs = unificationPredicate.getRhs(); boolean leftBound = boundVars.containsAll(lhs.varSet()); boolean rightBound = boundVars.containsAll(rhs.varSet()); if (unificationPredicate.isNegated() && !(leftBound && rightBound)) { throw new InvalidProgramException(); } if (leftBound && rightBound) { return Check.make(lhs, rhs, unificationPredicate.isNegated()); } else if (rightBound) { if (lhs instanceof Var) { if (Configuration.inlineInRules || rhs instanceof Var) { subst.put((Var) lhs, rhs); return null; } return Assignment.make((Var) lhs, rhs); } if (!(lhs instanceof Constructor)) { throw new InvalidProgramException(); } return makeDestructor(rhs, (Constructor) lhs, todo); } else if (leftBound) { if (rhs instanceof Var) { if (Configuration.inlineInRules || lhs instanceof Var) { subst.put((Var) rhs, lhs); return null; } return Assignment.make((Var) rhs, lhs); } if (!(rhs instanceof Constructor)) { throw new InvalidProgramException(); } return makeDestructor(lhs, (Constructor) rhs, todo); } else { if (!(lhs instanceof Constructor) || !(rhs instanceof Constructor)) { throw new InvalidProgramException(); } Constructor c1 = (Constructor) lhs; Constructor c2 = (Constructor) rhs; if (!c1.getSymbol().equals(c2.getSymbol())) { throw new InvalidProgramException("Unsatisfiable unification conjunct"); } List cs = new ArrayList<>(); Term[] args1 = c1.getArgs(); Term[] args2 = c2.getArgs(); for (int i = 0; i < args1.length; ++i) { cs.add(UnificationPredicate.make(args1[i], args2[i], false)); } // XXX Not reordering because of type soundness issues. // ValidRule.order(cs, (p, xs) -> 1, new HashSet<>(boundVars), varCounts); for (ComplexLiteral c : cs) { todo.add(c); } return null; } } private Destructor makeDestructor( Term boundTerm, Constructor unboundCtor, List todo) { Term[] args = unboundCtor.getArgs(); Var[] vars = new Var[args.length]; for (int i = 0; i < args.length; ++i) { Var y = Var.fresh(); vars[i] = y; todo.add(UnificationPredicate.make(y, args[i], false)); } return Destructor.make(boundTerm, unboundCtor.getSymbol(), vars); } @Override public SimpleLiteral visit(UserPredicate userPredicate, Void input) { Term[] args = userPredicate.getArgs(); Term[] newArgs = new Term[args.length]; Set seen = new HashSet<>(); for (int i = 0; i < args.length; ++i) { Term arg = args[i]; if (boundVars.containsAll(arg.varSet())) { newArgs[i] = arg; } else if (arg instanceof Var && seen.add((Var) arg)) { newArgs[i] = arg; } else { Var y = Var.fresh(); newArgs[i] = y; todo.add(UnificationPredicate.make(y, arg, false)); } } BindingType[] pat = computeBindingPattern(newArgs, boundVars, varCounts); SimpleLiteral c = SimplePredicate.make( userPredicate.getSymbol(), newArgs, pat, userPredicate.isNegated()); return c; } }, null); if (c != null) { acc.add(c); boundVars.addAll(c.varSet()); } for (ComplexLiteral x : todo) { add(x); } } public List getConjuncts() { return acc; } public Set getBoundVars() { return boundVars; } } } ================================================ FILE: src/main/resources/codegen/.gitignore ================================================ cmake-build-*/ ================================================ FILE: src/main/resources/codegen/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.21) project(codegen) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "-D__EMBEDDED_SOUFFLE__ -DRAM_DOMAIN_SIZE=64 -Wall -Wno-unused-variable -Wno-unused-but-set-variable -Wno-c++11-narrowing") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -march=native") add_executable(flg "") set(SOUFFLE_FLAGS -w -j 2 -PRamSIPS:strict) option(FLG_EAGER_EVAL "Generate code performing eager evaluation (requires custom Soufflé)" OFF) if (FLG_EAGER_EVAL) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFLG_EAGER_EVAL") set(SOUFFLE_FLAGS ${SOUFFLE_FLAGS} --eager-eval) endif () option(FLG_RECORD_WORK "Report amount of work performed during evaluation (requires custom Soufflé)" OFF) if (FLG_RECORD_WORK) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFLG_RECORD_WORK") set(SOUFFLE_FLAGS ${SOUFFLE_FLAGS} --record-work) endif () add_custom_command(OUTPUT formulog.cpp COMMAND souffle ${CMAKE_CURRENT_SOURCE_DIR}/src/formulog.dl ${SOUFFLE_FLAGS} -g formulog.cpp DEPENDS src/formulog.dl) target_sources(flg PRIVATE src/time.hpp src/ConcurrentHashMap.hpp src/funcs.hpp src/functors.cpp src/functors.h src/main.cpp src/parser.cpp src/parser.hpp src/Symbol.cpp src/Symbol.hpp src/Term.cpp src/Term.hpp src/Type.hpp src/Type.cpp src/globals.h src/set.cpp src/set.hpp src/smt_solver.cpp src/smt_solver.h src/smt_shim.cpp src/smt_shim.h src/smt_parser.cpp src/smt_parser.hpp formulog.cpp ) find_package(Boost 1.81 REQUIRED COMPONENTS filesystem system program_options) find_package(TBB REQUIRED) target_link_libraries(flg PRIVATE ${Boost_LIBRARIES} TBB::tbb) find_package(OpenMP) if (OpenMP_CXX_FOUND) target_link_libraries(flg PRIVATE OpenMP::OpenMP_CXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") endif () option(FLG_DEV "Formulog codegen development mode" OFF) if (FLG_DEV) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFLG_DEV") endif () ================================================ FILE: src/main/resources/codegen/src/ConcurrentHashMap.hpp ================================================ // // Created by Aaron Bembenek on 4/25/23. // #pragma once #include namespace flg { template, typename Equals = std::equal_to> using ConcurrentHashMap = tbb::concurrent_unordered_map; } ================================================ FILE: src/main/resources/codegen/src/Symbol.cpp ================================================ #include "Symbol.hpp" namespace flg { ostream& operator<<(ostream& out, Symbol sym) { switch (sym) { case Symbol::boxed_bool: return out << "boxed_bool"; case Symbol::boxed_i32: return out << "boxed_i32"; case Symbol::boxed_i64: return out << "boxed_i64"; case Symbol::boxed_fp32: return out << "boxed_fp32"; case Symbol::boxed_fp64: return out << "boxed_fp64"; case Symbol::boxed_string: return out << "boxed_string"; case Symbol::model: return out << "model"; case Symbol::opaque_set: return out << "opaque_set"; /* INSERT 0 */ } __builtin_unreachable(); } void initialize_symbols() { /* INSERT 1 */ } Symbol lookup_symbol(const string& name) { auto it = symbol_table.find(name); if (it != symbol_table.end()) { return it->second; } cerr << "Unrecognized symbol: " << name << endl; abort(); } Symbol lookup_tuple_symbol(size_t arity) { switch (arity) { /* INSERT 2 */ default: cerr << "Unrecognized tuple arity: " << arity << endl; abort(); } } } // namespace flg ================================================ FILE: src/main/resources/codegen/src/Symbol.hpp ================================================ #pragma once #include #include #include #include namespace flg { using namespace std; enum class Symbol { boxed_bool, boxed_i32, boxed_i64, boxed_fp32, boxed_fp64, boxed_string, model, opaque_set, #ifdef FLG_DEV nil, cons, smt_and, smt_not, smt_imp, smt_or, smt_var__bool__bool, #endif /* INSERT 0 */ }; inline map symbol_table; ostream &operator<<(ostream &out, Symbol sym); void initialize_symbols(); Symbol lookup_symbol(const string &name); Symbol lookup_tuple_symbol(size_t arity); constexpr size_t symbol_arity(Symbol sym) { switch (sym) { /* INSERT 1 */ default: abort(); } #ifdef FLG_DEV return 0; #endif } } // namespace flg ================================================ FILE: src/main/resources/codegen/src/Term.cpp ================================================ #include #include #include #include #include "Term.hpp" namespace flg { ostream &operator<<(ostream &out, const Term &t) { switch (t.sym) { case Symbol::boxed_bool: { return out << boolalpha << t.as_base().val << noboolalpha; } case Symbol::boxed_i32: { return out << t.as_base().val; } case Symbol::boxed_i64: { return out << t.as_base().val << "L"; } case Symbol::boxed_fp32: { auto val = t.as_base().val; if (isnan(val)) { out << "fp32_nan"; } else if (isinf(val)) { if (val > 0) { out << "fp32_pos_infinity"; } else { out << "fp32_neg_infinity"; } } else { out << val << "F"; } return out; } case Symbol::boxed_fp64: { auto val = t.as_base().val; if (isnan(val)) { out << "fp64_nan"; } else if (isinf(val)) { if (val > 0) { out << "fp64_pos_infinity"; } else { out << "fp64_neg_infinity"; } } else { out << val << "F"; } return out; } case Symbol::boxed_string: { return out << "\"" << t.as_base().val << "\""; } default: { auto& x = t.as_complex(); out << x.sym; size_t n = x.arity; if (n > 0) { out << "("; for (size_t i = 0; i < n; ++i) { out << *x.val[i]; if (i < n - 1) { out << ", "; } } out << ")"; } return out; } } } // Concurrency-safe cache for ComplexTerm values template class ComplexTermCache { inline static constexpr size_t arity = symbol_arity(S); typedef array key_t; typedef ConcurrentHashMap> map_t; inline static map_t cache; public: template > static term_ptr get(T... val) { array arr = {val...}; auto it = cache.find(arr); if (it != cache.end()) { return it->second; } auto *heap_arr = new term_ptr[arity]{val...}; auto ptr = new ComplexTerm(S, arity, heap_arr); auto result = cache.emplace(arr, ptr); if (!result.second) { // Term was not successfully inserted delete ptr; } return result.first->second; } }; template term_ptr Term::make(T... val) { return ComplexTermCache::get(val...); } /* INSERT 0 */ term_ptr ComplexTerm::fresh_smt_var() { #ifdef FLG_DEV return nullptr; #else return new ComplexTerm(Symbol::smt_var__bool__bool, 0, nullptr); #endif } term_ptr Term::make_generic(Symbol sym, const vector &terms) { if (symbol_arity(sym) != terms.size()) { string message = "Expected arity "; message += to_string(symbol_arity(sym)); message += " for symbol (ID "; message += to_string(static_cast(sym)); message += "), got arity "; message += to_string(terms.size()); throw std::runtime_error(message); } switch (sym) { /* INSERT 1 */ default: throw std::runtime_error( "Invalid symbol (ID " + to_string(static_cast(sym)) + ") used to construct term" ); } } } ================================================ FILE: src/main/resources/codegen/src/Term.hpp ================================================ #pragma once #include #include #include #include #include #include #include "set.hpp" #include "ConcurrentHashMap.hpp" #include "Symbol.hpp" #define NO_COPY_OR_ASSIGN(t) \ t(const t&) = delete; t(t&&) = delete; \ t& operator=(const t&) = delete; \ t& operator=(t&&) = delete; namespace flg { using namespace std; struct Term; typedef const Term *term_ptr; template struct BaseTerm; struct ComplexTerm; template class BaseTermCache; template class ComplexTermCache; struct Term { const Symbol sym; NO_COPY_OR_ASSIGN(Term); template [[nodiscard]] inline const BaseTerm &as_base() const; [[nodiscard]] inline const ComplexTerm &as_complex() const; // Construct a memoized BaseTerm template inline static term_ptr make(T val); template inline static term_ptr make_moved(T &&val); // Construct a memoized ComplexTerm template static term_ptr make(T... val); // Convert a Lisp-style list term into a vector inline static vector vectorize_list_term(term_ptr t); [[nodiscard]] souffle::RamDomain intize() const { return (souffle::RamDomain) (uintptr_t) (void *) this; } static term_ptr unintize(souffle::RamDomain id) { return (term_ptr) (void *) (uintptr_t) id; } static term_ptr make_generic(Symbol sym, const vector &terms); protected: explicit Term(Symbol sym_) : sym{sym_} {} }; struct ComplexTerm : public Term { const size_t arity; const term_ptr *const val; NO_COPY_OR_ASSIGN(ComplexTerm); static term_ptr fresh_smt_var(); private: ComplexTerm(Symbol sym_, size_t arity_, term_ptr *val_) : Term{sym_}, arity{arity_}, val{val_} {} ~ComplexTerm() { delete[] val; } template friend class ComplexTermCache; }; template struct BaseTerm : public Term { const T val; NO_COPY_OR_ASSIGN(BaseTerm); private: BaseTerm(Symbol sym_, T &&val_) : Term{sym_}, val{std::move(val_)} {} template friend class BaseTermCache; }; ostream &operator<<(ostream &out, const Term &t); // Concurrency-safe cache for BaseTerm values template class BaseTermCache { struct Hash { std::size_t operator()(const T *const &val) const { return boost::hash{}(*val); } }; struct Equals { bool operator()(const T *const &val1, const T *const &val2) const { return *val1 == *val2; } }; typedef ConcurrentHashMap map_t; inline static map_t cache; public: static term_ptr get(T &&val) { auto it = cache.find(&val); if (it != cache.end()) { return it->second; } auto ptr = new BaseTerm(S, std::move(val)); auto result = cache.emplace(&ptr->val, ptr); if (!result.second) { // Term was not successfully inserted delete ptr; } return result.first->second; } }; template<> inline term_ptr Term::make(bool val) { typedef BaseTermCache cache; // Optimization to avoid unnecessary lock contention static const term_ptr true_term = cache::get(true); static const term_ptr false_term = cache::get(false); return val ? true_term : false_term; } template<> inline term_ptr Term::make(int32_t val) { return BaseTermCache::get(std::move(val)); } template<> inline term_ptr Term::make(int64_t val) { return BaseTermCache::get(std::move(val)); } template<> inline term_ptr Term::make(float val) { typedef BaseTermCache cache; // NaN is a special case due to ill-behaved floating point comparison static const term_ptr nan32_term = cache::get(nanf("")); if (isnan(val)) { return nan32_term; } return cache::get(std::move(val)); } template<> inline term_ptr Term::make(double val) { typedef BaseTermCache cache; // NaN is a special case due to ill-behaved floating point comparison static const term_ptr nan64_term = cache::get(nan("")); if (isnan(val)) { return nan64_term; } return cache::get(std::move(val)); } template<> inline term_ptr Term::make(string val) { return BaseTermCache::get(std::move(val)); } template<> inline term_ptr Term::make_moved(string &&val) { return BaseTermCache::get(std::move(val)); } typedef std::map Model; template<> inline term_ptr Term::make_moved(Model &&val) { return BaseTermCache::get(std::move(val)); } template<> inline term_ptr Term::make_moved(Set &&val) { return BaseTermCache::get(std::move(val)); } template const BaseTerm &Term::as_base() const { return reinterpret_cast &>(*this); } const ComplexTerm &Term::as_complex() const { return reinterpret_cast(*this); } inline vector Term::vectorize_list_term(term_ptr t) { vector v; #ifndef FLG_DEV while (t->sym == Symbol::cons) { auto& x = t->as_complex(); v.push_back(x.val[0]); t = x.val[1]; } assert(t->sym == Symbol::nil); #endif return v; } } // namespace flg ================================================ FILE: src/main/resources/codegen/src/Tuple.hpp ================================================ #pragma once #include #include #include namespace flg { using namespace std; template struct Tuple { array val; inline const term_ptr& operator[](int idx) const { return val[idx]; } inline term_ptr& operator[](int idx) { return val[idx]; } inline bool operator<(const Tuple& other) const { for (size_t i = 0; i < N; ++i) { int cmp = Term::compare_natural((*this)[i], other[i]); if (cmp != 0) { return cmp < 0; } } return false; } }; template inline ostream& operator<<(ostream& out, const Tuple& tup) { out << "("; for (size_t i = 0; i < N; ++i) { out << *tup[i]; if (i < N - 1) { out << ", "; } } out << ")"; return out; } template inline void print_relation(const string& name, bool sorted, const Index& btree) { vector v(btree.begin(), btree.end()); if (sorted) { sort(v.begin(), v.end()); } for (const auto& tuple : v) { cout << name << tuple << '\n'; } cout << flush; } // Inspired by the comparator in souffle/CompiledIndexUtils.h template struct Comparator; template struct Comparator { template inline int operator()(const Tuple& a, const Tuple& b) const { int cmp = Term::compare(a[First], b[First]); return cmp ? cmp : Comparator()(a, b); } template inline bool less(const Tuple& a, const Tuple& b) const { int cmp = Term::compare(a[First], b[First]); return cmp ? cmp < 0 : Comparator().less(a, b); } template inline bool equal(const Tuple& a, const Tuple& b) const { int cmp = Term::compare(a[First], b[First]); return cmp ? false : Comparator().equal(a, b); } }; template <> struct Comparator<> { template inline int operator()(const Tuple& a, const Tuple& b) const { return 0; } template inline bool less(const Tuple& a, const Tuple& b) const { return false; } template inline bool equal(const Tuple& a, const Tuple& b) const { return true; } }; } // namespace flg ================================================ FILE: src/main/resources/codegen/src/Type.cpp ================================================ // // Created by Aaron Bembenek on 8/10/22. // #include "Type.hpp" namespace flg { void TypeSubst::put(const Type &var, const Type &other) { assert(var.is_var); if (other.is_var) { if (var < other) { m.emplace(var, other); } else if (other < var) { m.emplace(other, var); } } else { m.emplace(var, other); } } Type TypeSubst::apply(const Type &ty) { if (ty.is_var) { auto v = m.find(ty); if (v == m.end()) { return ty; } else { return apply(v->second); } } std::vector newArgs; for (auto &arg: ty.args) { newArgs.push_back(apply(arg)); } return Type{ty.name, ty.is_var, newArgs}; } void TypeSubst::clear() { m.clear(); } std::ostream &operator<<(std::ostream &out, const Type &type) { auto args = type.args; if (!args.empty()) { out << "("; } out << type.name; for (auto &arg: args) { out << " " << arg; } if (!args.empty()) { out << ")"; } return out; } functor_type Type::make_prim(const std::string &name, const std::vector &args) { return make_pair(std::vector(), Type{name, false, args}); } functor_type Type::make_prim(const std::string &name) { return make_prim(name, {}); } Type Type::make_index(const std::string &name) { return Type{name, false, {}}; } functor_type Type::bool_ = make_prim("Bool"); functor_type Type::i32 = make_prim("_ BitVec", {make_index("32")}); functor_type Type::i64 = make_prim("_ BitVec", {make_index("64")}); functor_type Type::fp32 = make_prim("_ FloatingPoint", {make_index("8"), make_index("24")}); functor_type Type::fp64 = make_prim("_ FloatingPoint", {make_index("11"), make_index("53")}); functor_type Type::string_ = make_prim("String"); atomic_size_t Type::cnt; Type Type::new_var() { return Type{"x" + to_string(cnt++), true, {}}; } functor_type Type::lookup(Symbol sym) { switch (sym) { case Symbol::boxed_bool: return bool_; case Symbol::boxed_i32: return i32; case Symbol::boxed_i64: return i64; case Symbol::boxed_fp32: return fp32; case Symbol::boxed_fp64: return fp64; case Symbol::boxed_string: return string_; case Symbol::model: case Symbol::opaque_set: throw std::runtime_error("shouldn't happen"); /* INSERT 0 */ } __builtin_unreachable(); } } ================================================ FILE: src/main/resources/codegen/src/Type.hpp ================================================ #pragma once #include #include #include #include #include #include #include "Symbol.hpp" namespace flg { struct Type; struct TypeSubst; typedef std::pair, Type> functor_type; struct Type { string name; bool is_var; std::vector args; static functor_type lookup(Symbol sym); static functor_type i32; static functor_type i64; static functor_type fp32; static functor_type fp64; static functor_type string_; static functor_type bool_; private: static functor_type make_prim(const std::string &name); static functor_type make_prim(const std::string &name, const std::vector &args); static Type make_index(const std::string &name); static Type new_var(); static std::atomic_size_t cnt; }; inline bool operator<(const Type &lhs, const Type &rhs) { return lhs.name < rhs.name; } struct TypeSubst { void put(const Type &var, const Type &other); Type apply(const Type &type); void clear(); private: std::map m; }; ostream &operator<<(ostream &out, const Type &type); } // namespace flg ================================================ FILE: src/main/resources/codegen/src/formulog.dl ================================================ ================================================ FILE: src/main/resources/codegen/src/funcs.hpp ================================================ #pragma once #include #include #include #include #include #include "globals.h" #include "Term.hpp" #include "smt_solver.h" namespace flg { namespace funcs { using namespace std; template term_ptr __access(term_ptr t1) { return t1->as_complex().val[N]; } term_ptr beq(term_ptr t1, term_ptr t2) { return Term::make(t1 == t2); } term_ptr bneq(term_ptr t1, term_ptr t2) { return Term::make(t1 != t2); } term_ptr bnot(term_ptr t1) { return Term::make(!t1->as_base().val); } template term_ptr __add(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val + t2->as_base().val); } template term_ptr __sub(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val - t2->as_base().val); } template term_ptr __mul(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val * t2->as_base().val); } template term_ptr __div(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val / t2->as_base().val); } template term_ptr __rem(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val % t2->as_base().val); } template term_ptr __bitwise_and(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val & t2->as_base().val); } template term_ptr __bitwise_or(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val | t2->as_base().val); } template term_ptr __bitwise_xor(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val ^ t2->as_base().val); } template term_ptr __shl(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val << t2->as_base().val); } template term_ptr __ashr(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val >> t2->as_base().val); } template term_ptr __lshr(term_ptr t1, term_ptr t2) { U res = t1->as_base().val >> t2->as_base().val; return Term::make(reinterpret_cast(res)); } template term_ptr __urem(term_ptr t1, term_ptr t2) { U res = t1->as_base().val % t2->as_base().val; return Term::make(reinterpret_cast(res)); } template term_ptr __udiv(term_ptr t1, term_ptr t2) { U res = t1->as_base().val / t2->as_base().val; return Term::make(reinterpret_cast(res)); } template term_ptr __neg(term_ptr t1) { return Term::make(-t1->as_base().val); } template term_ptr __eq(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val == t2->as_base().val); } template term_ptr __lt(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val < t2->as_base().val); } template term_ptr __le(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val <= t2->as_base().val); } template term_ptr __gt(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val > t2->as_base().val); } template term_ptr __ge(term_ptr t1, term_ptr t2) { return Term::make(t1->as_base().val >= t2->as_base().val); } template term_ptr __cmp(term_ptr t1, term_ptr t2) { #ifdef FLG_DEV return nullptr; #else auto xval = t1->as_base().val; auto yval = t2->as_base().val; if (xval < yval) { return Term::make(); } else if (xval > yval) { return Term::make(); } else { return Term::make(); } #endif } term_ptr print(term_ptr t1) { cout << *t1 << endl; return Term::make(true); } term_ptr string_concat(term_ptr t1, term_ptr t2) { return Term::make_moved(t1->as_base().val + t2->as_base().val); } term_ptr string_matches(term_ptr t1, term_ptr t2) { regex re(t2->as_base().val); return Term::make(regex_match(t1->as_base().val, re)); } term_ptr string_starts_with(term_ptr t1, term_ptr t2) { auto str = t1->as_base().val; auto pre = t2->as_base().val; if (pre.size() > str.size()) { return Term::make(false); } auto res = mismatch(pre.begin(), pre.end(), str.begin()); return Term::make(res.first == pre.end()); } term_ptr _make_some(term_ptr t) { #ifdef FLG_DEV return nullptr; #else return Term::make(t); #endif } term_ptr _make_none() { #ifdef FLG_DEV return nullptr; #else return Term::make(); #endif } term_ptr char_at(term_ptr s, term_ptr i) { auto str = s->as_base().val; auto pos = i->as_base().val; if (pos < 0 || (size_t) pos >= str.size()) { return _make_none(); } return _make_some(Term::make(str[pos])); } term_ptr string_length(term_ptr s) { return Term::make(s->as_base().val.size()); } term_ptr vec_to_term_list(const std::vector &v) { #ifdef FLG_DEV return nullptr; #else term_ptr acc = Term::make(); for (auto it = v.rbegin(); it != v.rend(); ++it) { acc = Term::make(*it, acc); } return acc; #endif } term_ptr string_to_list(term_ptr s) { vector v; auto str = s->as_base().val; for (char ch: str) { v.push_back(Term::make(ch)); } return vec_to_term_list(v); } term_ptr list_to_string(term_ptr l) { vector v = Term::vectorize_list_term(l); string str; for (auto sub: v) { str += (char) sub->as_base().val; } return Term::make_moved(std::move(str)); } term_ptr substring(term_ptr str_term, term_ptr start_term, term_ptr len_term) { auto str = str_term->as_base().val; auto start = start_term->as_base().val; auto len = len_term->as_base().val; if (start < 0 || len < 0 || str.size() < start + (size_t) len) { return _make_none(); } return _make_some(Term::make_moved(str.substr(start, len))); } term_ptr string_to_i32(term_ptr str_term) { auto str = str_term->as_base().val; static regex dec("[+-]?\\d+"); static regex hex("0x[0-9a-fA-F]+"); try { if (regex_match(str, dec)) { long r = stol(str); if (r >= INT32_MIN && r <= INT32_MAX) { return _make_some(Term::make((int32_t) r)); } } else if (regex_match(str, hex)) { unsigned long r = stoul(str, nullptr, 16); if (r <= UINT32_MAX) { return _make_some(Term::make((int32_t) r)); } } } catch (out_of_range &_) { // fall through } return _make_none(); } term_ptr string_to_i64(term_ptr str_term) { auto str = str_term->as_base().val; static regex dec("[+-]?\\d+"); static regex hex("0x[0-9a-fA-F]+"); try { if (regex_match(str, dec)) { long long r = stoll(str); if (r >= INT64_MIN && r <= INT64_MAX) { return _make_some(Term::make((int64_t) r)); } } else if (regex_match(str, hex)) { unsigned long long r = stoull(str, nullptr, 16); if (r <= UINT64_MAX) { return _make_some(Term::make((int64_t) r)); } } } catch (out_of_range &_) { // fall through } return _make_none(); } term_ptr to_string(term_ptr t1) { if (t1->sym == Symbol::boxed_string) { return t1; } stringstream ss; ss << *t1; return Term::make_moved(ss.str()); } template term_ptr __conv(term_ptr t1) { return Term::make(t1->as_base().val); } term_ptr is_sat(term_ptr t1) { switch (smt::smt_solver.check(t1).status) { case smt::SmtStatus::sat: return Term::make(true); case smt::SmtStatus::unsat: return Term::make(false); case smt::SmtStatus::unknown: throw runtime_error("SMT returned `unknown`"); } __builtin_unreachable(); } term_ptr _make_smt_not(term_ptr t) { #ifdef FLG_DEV return nullptr; #else return Term::make(t); #endif } term_ptr is_valid(term_ptr t1) { switch (smt::smt_solver.check(_make_smt_not(t1)).status) { case smt::SmtStatus::sat: return Term::make(false); case smt::SmtStatus::unsat: return Term::make(true); case smt::SmtStatus::unknown: abort(); } __builtin_unreachable(); } int32_t _extract_timeout_from_option(term_ptr o) { int32_t timeout{numeric_limits::max()}; #ifndef FLG_DEV if (o->sym == Symbol::some) { auto &x = o->as_complex(); timeout = x.val[0]->as_base().val; } #endif return timeout; } term_ptr is_sat_opt(term_ptr t1, term_ptr t2) { int timeout = _extract_timeout_from_option(t2); auto assertions = Term::vectorize_list_term(t1); std::reverse(assertions.begin(), assertions.end()); switch (smt::smt_solver.check(assertions, false, timeout).status) { case smt::SmtStatus::sat: return _make_some(Term::make(true)); case smt::SmtStatus::unsat: return _make_some(Term::make(false)); case smt::SmtStatus::unknown: return _make_none(); } __builtin_unreachable(); } term_ptr is_set_sat(term_ptr t1, term_ptr t2) { int timeout = _extract_timeout_from_option(t2); auto &s = t1->as_base().val; std::vector assertions{s.begin(), s.end()}; switch (smt::smt_solver.check(assertions, false, timeout).status) { case smt::SmtStatus::sat: return _make_some(Term::make(true)); case smt::SmtStatus::unsat: return _make_some(Term::make(false)); case smt::SmtStatus::unknown: return _make_none(); } __builtin_unreachable(); } term_ptr get_model(term_ptr t1, term_ptr t2) { int timeout = _extract_timeout_from_option(t2); auto assertions = Term::vectorize_list_term(t1); std::reverse(assertions.begin(), assertions.end()); auto res = smt::smt_solver.check(assertions, true, timeout); switch (res.status) { case smt::SmtStatus::sat: return _make_some(Term::make_moved(std::move(res.model.value()))); case smt::SmtStatus::unsat: case smt::SmtStatus::unknown: return _make_none(); } __builtin_unreachable(); } term_ptr query_model(term_ptr t1, term_ptr t2) { Model m = t2->as_base().val; auto it = m.find(t1); if (it != m.end()) { return _make_some(it->second); } return _make_none(); } std::vector make_int_key(std::vector key) { unsigned arity = key.size(); std::vector intKey(arity); for (unsigned i = 0; i < arity; ++i) { if (key[i]) { intKey[i] = key[i]->intize(); } } return intKey; } term_ptr _relation_contains(const std::string &relname, const std::vector &key) { auto rel = globals::program->getRelation(relname); assert(rel); size_t arity = rel->getPrimaryArity(); assert(arity == key.size()); std::vector intKey = make_int_key(key); for (auto &tup: *rel) { bool match = true; for (unsigned i = 0; i < arity; ++i) { match &= !key[i] || intKey[i] == tup[i]; } if (match) { return Term::make(true); } } return Term::make(false); } term_ptr _relation_contains_complete(const std::string &relname, const std::vector &key) { auto rel = globals::program->getRelation(relname); assert(rel); size_t arity = rel->getPrimaryArity(); assert(arity == key.size()); std::vector intKey = make_int_key(key); souffle::tuple tup(rel); for (auto arg: intKey) { tup << arg; } return Term::make(rel->contains(tup)); } term_ptr _relation_agg_mono(const std::string &relname, const std::vector &key, unsigned pos) { auto rel = globals::program->getRelation(relname); assert(rel); size_t arity = rel->getPrimaryArity(); assert(arity == key.size()); assert(pos < arity); std::vector intKey = make_int_key(key); std::vector v; for (auto &tup: *rel) { bool match = true; for (unsigned i = 0; i < arity; ++i) { match &= !key[i] || intKey[i] == tup[i]; } if (match) { v.push_back(Term::unintize(tup[pos])); } } return vec_to_term_list(v); } template term_ptr _relation_agg_poly(const std::string &relname, const std::vector &key, const std::vector &projection) { auto rel = globals::program->getRelation(relname); assert(rel); size_t arity = rel->getPrimaryArity(); assert(arity == key.size()); assert(projection.size() == arity); std::vector intKey = make_int_key(key); std::vector v; for (auto &tup: *rel) { bool match = true; for (unsigned i = 0; i < arity; ++i) { match &= !key[i] || intKey[i] == tup[i]; } if (match) { std::vector args; for (unsigned i = 0; i < arity; ++i) { if (projection[i]) { args.push_back(Term::unintize(tup[i])); } } v.push_back(Term::make_generic(S, args)); } } return vec_to_term_list(v); } term_ptr opaque_set_empty() { return Term::make_moved(set::empty()); } term_ptr opaque_set_plus(term_ptr val, term_ptr set) { auto &s = set->as_base().val; return Term::make_moved(set::plus(val, s)); } term_ptr opaque_set_minus(term_ptr val, term_ptr set) { auto &s = set->as_base().val; return Term::make_moved(set::minus(val, s)); } term_ptr opaque_set_union(term_ptr set1, term_ptr set2) { auto &s1 = set1->as_base().val; auto &s2 = set2->as_base().val; return Term::make_moved(set::plus_all(s1, s2)); } term_ptr opaque_set_diff(term_ptr set1, term_ptr set2) { auto &s1 = set1->as_base().val; auto &s2 = set2->as_base().val; return Term::make_moved(set::minus_all(s1, s2)); } term_ptr opaque_set_choose(term_ptr set) { auto &s = set->as_base().val; auto opt = set::choose(s); if (opt.has_value()) { auto p = opt.value(); auto t = p.first; auto r = Term::make_moved(std::move(p.second)); auto sym = lookup_tuple_symbol(2); return _make_some(Term::make_generic(sym, {t, r})); } else { return _make_none(); } } term_ptr opaque_set_size(term_ptr set) { auto &s = set->as_base().val; return Term::make((int32_t) set::size(s)); } term_ptr opaque_set_member(term_ptr val, term_ptr set) { auto &s = set->as_base().val; return Term::make(set::member(val, s)); } term_ptr opaque_set_singleton(term_ptr val) { return Term::make_moved(set::singleton(val)); } term_ptr opaque_set_subset(term_ptr set1, term_ptr set2) { auto &s1 = set1->as_base().val; auto &s2 = set2->as_base().val; return Term::make(set::subset(s1, s2)); } term_ptr opaque_set_from_list(term_ptr list) { auto vec = Term::vectorize_list_term(list); return Term::make_moved(set::from_vec(vec)); } // The template varargs is necessary to handle folding with closures (captured variables are passed in as additional // arguments). template term_ptr fold(term_ptr (*f)(term_ptr, term_ptr, Ts...), term_ptr acc, term_ptr list, Ts... args) { auto vec = Term::vectorize_list_term(list); for (auto t : vec) { acc = f(acc, t, args...); } return acc; } /* INSERT 0 */ } // namespace funcs /* INSERT 1 */ } // namespace flg ================================================ FILE: src/main/resources/codegen/src/functors.cpp ================================================ #include "functors.h" #include "funcs.hpp" using namespace flg; using namespace std; souffle::RamDomain nth(souffle::RamDomain n, souffle::RamDomain ref, souffle::RamDomain check) { assert(ref && check); return Term::unintize(ref)->as_complex().val[n]->intize(); } /* INSERT 0 */ ================================================ FILE: src/main/resources/codegen/src/functors.h ================================================ #pragma once #include #include "Term.hpp" namespace flg { template inline souffle::RamDomain dtor(term_ptr t) { return t->sym == S; } } extern "C" { souffle::RamDomain nth(souffle::RamDomain n, souffle::RamDomain ref, souffle::RamDomain check); /* INSERT 0 */ } ================================================ FILE: src/main/resources/codegen/src/globals.h ================================================ #pragma once #include #include #include "smt_solver.h" #include "time.hpp" namespace flg::globals { inline souffle::SouffleProgram *program{nullptr}; inline smt::SmtSolverMode smt_solver_mode{smt::SmtSolverMode::check_sat_assuming}; inline bool smt_double_check{true}; inline size_t smt_cache_size{100}; inline bool smt_stats{false}; struct smt_stats_t { time_t time; unsigned long long ncalls; friend smt_stats_t &operator+=(smt_stats_t &stats, time_t time) { stats.time += time; stats.ncalls++; return stats; } }; inline tbb::combinable smt_time; inline tbb::combinable smt_wait_time; inline tbb::combinable smt_cache_hits; inline tbb::combinable smt_cache_misses; inline tbb::combinable smt_cache_clears; template T sum(tbb::combinable &stat) { return stat.combine([](T x, T y) -> T { return x + y; }); } } ================================================ FILE: src/main/resources/codegen/src/main.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include "parser.hpp" #include "functors.h" #include "globals.h" using namespace flg; using namespace std; struct ExternalEdbLoader { explicit ExternalEdbLoader(size_t nthreads) : pool(nthreads) {} void go(const vector &dirs); private: boost::asio::thread_pool pool; void loadEdbs(const string &dir); void loadEdbs(const string &dir, const string &file, souffle::Relation *rel); }; void ExternalEdbLoader::go(const vector &dirs) { for (auto &dir: dirs) { loadEdbs(dir); } pool.join(); } void ExternalEdbLoader::loadEdbs(const string &dir) { /* INSERT 0 */ } void ExternalEdbLoader::loadEdbs(const string &dir, const string &file, souffle::Relation *rel) { assert(rel); boost::filesystem::path path{dir}; path /= file; string pathstr = path.string(); boost::asio::post(pool, [pathstr, rel]() { ifstream stream(pathstr); parse_facts(stream, *rel); stream.close(); }); } void loadFact(const string &relname, const vector &args) { auto rel = globals::program->getRelation(relname); assert(rel); souffle::tuple tup(rel); for (auto arg: args) { tup << arg->intize(); } rel->insert(tup); } void loadEdbs(const vector &dirs, size_t nthreads) { ExternalEdbLoader(nthreads).go(dirs); /* INSERT 1 */ } void printBanner(const std::string &heading) { std::cout << "==================== " << heading << " ====================\n"; } void printSmallBanner(const std::string &heading) { std::cout << "---------- " << heading << " ----------\n"; } void printSizes() { std::cout << "\n"; printBanner("RELATION SIZES"); for (auto rel: globals::program->getOutputRelations()) { std::string name = rel->getName(); if (name.find("CODEGEN_") == 0) { continue; } name = name.substr(0, name.size() - 1); std::cout << name << ": " << rel->size() << std::endl; } } void printResults() { std::cout << "\n"; printBanner("SELECTED IDB RELATIONS"); for (auto rel: globals::program->getOutputRelations()) { std::string name = rel->getName(); if (name.find("CODEGEN_") == 0) { continue; } name = name.substr(0, name.size() - 1); std::stringstream ss; ss << name << " (" << rel->size() << ")"; std::cout << "\n"; printSmallBanner(ss.str()); for (auto &tup: *rel) { std::cout << name << "("; for (size_t i = 0; i < rel->getPrimaryArity(); ++i) { std::cout << *Term::unintize(tup[i]); if (i < rel->getPrimaryArity() - 1) { std::cout << ", "; } } std::cout << ")" << std::endl; } } } struct ExternalIdbPrinter { ExternalIdbPrinter(boost::filesystem::path dir_, size_t nthreads) : dir(std::move(dir_)), pool(nthreads) {} void go(); private: const boost::filesystem::path dir; boost::asio::thread_pool pool; void saveToDisk(const string &name); }; void ExternalIdbPrinter::saveToDisk(const string &name) { auto rel = globals::program->getRelation(name); assert(rel); auto path(dir); auto corrected_name = name.substr(0, name.size() - 1); path /= corrected_name; auto pathstr = path.string() + string(".tsv"); boost::asio::post(pool, [pathstr, rel]() { ofstream ofs(pathstr); for (auto &tup: *rel) { for (size_t i = 0; i < rel->getPrimaryArity(); ++i) { ofs << *Term::unintize(tup[i]); if (i < rel->getPrimaryArity() - 1) { ofs << "\t"; } } ofs << "\n"; } ofs.flush(); ofs.close(); }); } void ExternalIdbPrinter::go() { /* INSERT 2 */ pool.join(); } namespace std { std::ostream &operator<<(std::ostream &os, const std::vector &vec) { for (auto &item: vec) { os << item << " "; } return os; } } template std::string join(const std::vector &v) { if (v.empty()) { return ""; } std::stringstream ss; auto it = v.begin(); while (true) { ss << *it++; if (it == v.end()) { break; } ss << ","; } return ss.str(); } void printSmtStats() { std::vector times; std::vector calls; flg::time_t total_time; double total_calls; globals::smt_time.combine_each([&](auto stats) { times.push_back(stats.time.count()); total_time += stats.time; calls.push_back(stats.ncalls); total_calls += stats.ncalls; }); std::cout << "\n"; printBanner("SMT STATS"); std::cout << "SMT calls: " << total_calls << "\n"; std::cout << "SMT time (ms): " << total_time.count() << std::endl; std::cout << "SMT wait time (ms): " << globals::sum(globals::smt_wait_time).count() << std::endl; std::cout << "SMT cache hits: " << globals::sum(globals::smt_cache_hits) << std::endl; std::cout << "SMT cache misses: " << globals::sum(globals::smt_cache_misses) << std::endl; std::cout << "SMT cache clears: " << globals::sum(globals::smt_cache_clears) << std::endl; std::cout << "SMT calls per solver: " << join(calls) << std::endl; std::cout << "SMT time per solver (ms): " << join(times) << std::endl; } int main(int argc, char **argv) { namespace po = boost::program_options; po::options_description desc("Allowed options"); string cwd = boost::filesystem::current_path().string(); bool dump_idb{false}; bool dump_sizes{false}; bool no_smt_double_check{false}; desc.add_options() ("help,h", "produce help message") ("parallelism,j", po::value()->default_value(1), "number of threads to use") ("dump-idb", po::bool_switch(&dump_idb), "print the contents of the IDB relations to screen") ("dump-sizes", po::bool_switch(&dump_sizes), "print relation sizes") ("fact-dir,F", po::value>()->default_value({cwd}), "input directory with external EDBs (can be set multiple times)") ("out-dir,D", po::value()->default_value(cwd), "directory for .tsv output files") ("smt-solver-mode", po::value()->default_value(smt::SmtSolverMode::check_sat_assuming), "set interaction strategy between Formulog and the external SMT solver") ("no-smt-double-check", po::bool_switch(&no_smt_double_check), "do not double check unknown values returned by SMT solver (using a generally more reliable solver mode)") ("smt-cache-size", po::value()->default_value(100), "how many implications to store for check-sat-assuming solver") ("smt-stats", po::bool_switch(&globals::smt_stats), "report basic statistics related to SMT solver usage"); po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); if (vm.count("help")) { cout << desc << endl; return 1; } size_t parallelism = vm["parallelism"].as(); if (parallelism == 0) { cout << "Cannot use 0 threads" << endl; return 1; } initialize_symbols(); globals::smt_solver_mode = vm["smt-solver-mode"].as(); globals::smt_double_check = !no_smt_double_check; globals::program = souffle::ProgramFactory::newInstance("formulog"); if (!globals::program) { cout << "Unable to load Souffle program" << endl; exit(1); } cout << "Parsing..." << endl; auto start = std::chrono::steady_clock::now(); loadEdbs(vm["fact-dir"].as>(), parallelism); auto end = chrono::steady_clock::now(); double diff = chrono::duration_cast(end - start).count() / 1000.0; cout << "Finished parsing (" << boost::format("%.3f") % diff << "s)" << endl; cout << "Evaluating..." << endl; globals::program->setNumThreads(parallelism); start = std::chrono::steady_clock::now(); globals::program->run(); end = chrono::steady_clock::now(); diff = chrono::duration_cast(end - start).count() / 1000.0; cout << "Finished evaluating (" << boost::format("%.3f") % diff << "s)" << endl; flg::smt::SmtLibShim::terminate_all_children(); boost::filesystem::path out_dir(vm["out-dir"].as()); boost::filesystem::create_directories(out_dir); ExternalIdbPrinter idbPrinter(out_dir, parallelism); idbPrinter.go(); if (globals::smt_stats) { printSmtStats(); } if (dump_sizes) { printSizes(); } if (dump_idb) { printResults(); } #ifdef FLG_RECORD_WORK cout << "[WORK] " << globals::sum(souffle::work) << std::endl; #endif std::_Exit(EXIT_SUCCESS); } ================================================ FILE: src/main/resources/codegen/src/parser.cpp ================================================ #include #include #include #include #include #include #include #include "parser.hpp" namespace flg { namespace parser { using boost::optional; // A class that represents the intermediate parsing state of a term class TermParser { const string &buffer; size_t pos; public: TermParser(const string &term) : buffer(term), pos(0) {} inline void take_whitespace(); inline bool take_string(const string &str); inline optional take_regex(const regex &rgx); inline char peek(); inline bool empty(); inline term_ptr take_term(); inline vector take_terms(); inline term_ptr take_tuple_or_parens(); inline term_ptr take_list(); inline term_ptr take_constructor(); inline term_ptr take_string(); inline term_ptr take_numeric(); }; inline void TermParser::take_whitespace() { while (pos < buffer.size() && buffer[pos] == ' ') { ++pos; } } inline bool TermParser::take_string(const string &str) { if ( pos + str.size() <= buffer.size() && equal(str.begin(), str.end(), buffer.begin() + pos) ) { pos += str.size(); return true; } return false; } inline optional TermParser::take_regex(const regex &rgx) { smatch m; if (regex_search(buffer.begin() + pos, buffer.end(), m, rgx)) { pos += m.length(); return m; } return {}; } inline char TermParser::peek() { take_whitespace(); if (pos == buffer.size()) { throw parsing_error("Unexpected end of term"); } return buffer[pos]; } inline bool TermParser::empty() { take_whitespace(); return pos == buffer.size(); } inline term_ptr TermParser::take_term() { take_whitespace(); term_ptr retval; // Determine type of term based on first character char c = peek(); if (c == '"') { retval = take_string(); } else if (c == '+' || c == '-' || ('0' <= c && c <= '9')) { retval = take_numeric(); } else if (c == '(') { retval = take_tuple_or_parens(); } else if (c == '[') { retval = take_list(); } else if (c >= 'a' && c <= 'z') { retval = take_constructor(); } else { throw parsing_error(string("Unexpected character: ") + c); } // Handle right-associative cons (::) operator if (!empty() && peek() == ':') { ++pos; if (peek() != ':') { throw parsing_error("Found first ':', expected a second ':' for cons"); } ++pos; #ifndef FLG_DEV return Term::make(retval, take_term()); #endif } return retval; } inline vector TermParser::take_terms() { vector retval; retval.push_back(take_term()); while (!empty() && peek() == ',') { ++pos; retval.push_back(take_term()); } return retval; } inline term_ptr TermParser::take_tuple_or_parens() { assert(buffer[pos] == '('); // Sanity check ++pos; vector terms = take_terms(); if (peek() != ')') { throw parsing_error("Expected character ')' to close parens"); } ++pos; if (terms.size() == 1) { // Parentheses case return terms[0]; } // Tuple case auto sym = lookup_tuple_symbol(terms.size()); return Term::make_generic(sym, terms); } inline term_ptr TermParser::take_list() { assert(buffer[pos] == '['); // Sanity check ++pos; // Case: empty list (equivalent to nil) if (peek() == ']') { ++pos; #ifndef FLG_DEV return Term::make(); #endif } vector terms = take_terms(); if (peek() != ']') { throw parsing_error("Expected character ']' to close list"); } ++pos; // Construct the list #ifdef FLG_DEV term_ptr retval = nullptr; #else term_ptr retval = Term::make(); for (auto it = terms.crbegin(); it != terms.crend(); ++it) { retval = Term::make(*it, retval); } #endif return retval; } inline term_ptr TermParser::take_constructor() { static const unordered_map literals = { {"true", Term::make(true)}, {"false", Term::make(false)}, {"true", Term::make(true)}, {"fp32_nan", Term::make(nanf(""))}, {"fp32_pos_infinity", Term::make(INFINITY)}, {"fp32_neg_infinity", Term::make(-INFINITY)}, {"fp64_nan", Term::make(nan(""))}, {"fp64_pos_infinity", Term::make(INFINITY)}, {"fp64_neg_infinity", Term::make(-INFINITY)}, }; string name; while (pos < buffer.size()) { char c = buffer[pos]; if ( ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || (c == '_') ) { name += c; ++pos; } else { break; } } const auto it = literals.find(name); if (it != literals.end()) { return it->second; } auto sym = lookup_symbol(name); vector terms; if (!empty() && peek() == '(') { ++pos; if (symbol_arity(sym) != 0) { terms = take_terms(); } if (peek() != ')') { throw parsing_error("Expected ')' to close constructor"); } ++pos; } return Term::make_generic(sym, terms); } inline term_ptr TermParser::take_string() { // Compatibility Note: // Currently, the Formulog parser does not support escape sequences, so I am // also not supporting escape sequences in the parsing step. This makes it // impossible to create strings with the characters \", \n, \t, \r, etc. assert(buffer[pos] == '"'); ++pos; string contents; while (pos < buffer.size() && buffer[pos] != '"') { contents += buffer[pos++]; } if (pos == buffer.size()) { throw parsing_error("Could not detect end of string literal"); } ++pos; return Term::make_moved(std::move(contents)); } inline term_ptr TermParser::take_numeric() { // Parse sign int sign = 1; const char signchar = peek(); if (signchar == '+' || signchar == '-') { if (signchar == '-') { sign = -1; } ++pos; } // Handle hexadecimal case static const regex hex_re(R"(^0(?:x|X)([0-9a-fA-F]+)([Ll]?)\b)"); if (auto match = take_regex(hex_re)) { if (match->length(2)) { auto value = stoll(match->str(1), nullptr, 16); return Term::make(sign * value); } else { auto value = stoi(match->str(1), nullptr, 16); return Term::make(sign * value); } } // Handle decimal case static const regex dec_re(R"(^([0-9]+)([Ll]?)\b(?!\.))"); if (auto match = take_regex(dec_re)) { if (match->length(2)) { auto value = stoll(match->str(1)); return Term::make(sign * value); } else { auto value = stoi(match->str(1)); return Term::make(sign * value); } } // Handle floating-point case static const regex fp_re( R"(^((?:[0-9]*\.[0-9]+|[0-9]+)(?:[Ee][+-]?[0-9]+)?)([FfDd]?)\b)" ); if (auto match = take_regex(fp_re)) { string suffix = match->str(2); if (suffix == "f" || suffix == "F") { auto value = stof(match->str(1)); return Term::make(sign * value); } else { auto value = stod(match->str(1)); return Term::make(sign * value); } } throw parsing_error("Could not identify numeric value"); } term_ptr parse_term(const string &term) { TermParser context(term); term_ptr retval = context.take_term(); if (!context.empty()) { throw parsing_error("Unexpected trailing characters in term: " + term); } return retval; } } // namespace parser } // namespace flg ================================================ FILE: src/main/resources/codegen/src/parser.hpp ================================================ #pragma once #include #include #include #include #include "Term.hpp" namespace flg { namespace parser { using namespace std; // Exception class thrown when parsing facts class parsing_error : public logic_error { using logic_error::logic_error; }; term_ptr parse_term(const string &term); inline void parse_facts(istream &in, souffle::Relation &rel) { // Parse each fact of the stream on a new line size_t arity = rel.getPrimaryArity(); string line; while (getline(in, line)) { istringstream ss(line); souffle::tuple tup(&rel); // Tab-separated term format string term; size_t count = 0; while (getline(ss, term, '\t')) { if (count >= arity) { throw parsing_error("Too many terms in tab-separated line"); } tup << parse_term(term)->intize(); count++; } if (count != arity) { throw parsing_error("Too few terms in tab-separated line"); } rel.insert(tup); } } } // namespace parser using parser::parse_facts; } // namespace flg ================================================ FILE: src/main/resources/codegen/src/set.cpp ================================================ // // Created by Aaron Bembenek on 2/10/23. // #include "set.hpp" namespace flg::set { Set empty() { return {}; } Set singleton(term_ptr val) { return { val }; } Set plus(term_ptr val, const Set &set) { Set r{set}; r.emplace(val); return r; } Set minus(term_ptr val, const Set &set) { Set r{set}; r.erase(val); return r; } Set plus_all(const Set &s1, const Set &s2) { Set r{s1}; r.insert(boost::container::ordered_unique_range, s2.begin(), s2.end()); return r; } Set minus_all(const Set &s1, const Set &s2) { Set r{s1}; auto it = r.begin(); for (auto elt : s2) { it = std::lower_bound(it, r.end(), elt); if (it == r.end()) { break; } if (*it != elt) { continue; } it = r.erase(it); } return r; } bool member(term_ptr val, const Set &set) { return set.contains(val); } std::size_t size(const Set &set) { return set.size(); } bool subset(const Set &s1, const Set &s2) { auto it = s2.begin(); for (auto elt : s1) { it = std::lower_bound(it, s2.end(), elt); if (it == s2.end() || *it != elt) { return false; } } return true; } std::optional> choose(const Set &s) { if (s.empty()) { return {}; } Set r{s}; auto it = r.end(); --it; auto t = *it; r.erase(it); return {{t, r}}; } Set from_vec(const std::vector &vec) { return {vec.begin(), vec.end()}; } } ================================================ FILE: src/main/resources/codegen/src/set.hpp ================================================ // // Created by Aaron Bembenek on 2/10/23. // #ifndef SMT_PARSER_CPP_SET_H #define SMT_PARSER_CPP_SET_H #include #include #include namespace flg { struct Term; typedef const flg::Term *term_ptr; typedef boost::container::flat_set Set; namespace set { Set empty(); Set singleton(term_ptr val); Set plus(term_ptr val, const Set &set); Set minus(term_ptr val, const Set &set); Set plus_all(const Set &s1, const Set &s2); Set minus_all(const Set &s1, const Set &s2); bool member(term_ptr val, const Set &set); std::size_t size(const Set &set); bool subset(const Set &s1, const Set &s2); std::optional> choose(const Set &s); Set from_vec(const std::vector &vec); } } #endif //SMT_PARSER_CPP_SET_H ================================================ FILE: src/main/resources/codegen/src/smt_parser.cpp ================================================ // // Created by Aaron Bembenek on 1/23/23. // #include "smt_parser.hpp" #include namespace flg { void SmtLibTokenizer::load(bool allow_eof) { if (m_next.has_value()) { return; } int ch; while (std::isspace(ch = m_is.get()) && m_ignore_whitespace) { // keep looping } if (ch == std::char_traits::eof()) { if (allow_eof) { return; } else { throw std::runtime_error("SMT-LIB tokenization error: unexpected EOF"); } } std::string s; s.push_back((char) ch); if (is_word_char(ch)) { while ((ch = m_is.peek()) != std::char_traits::eof() && is_word_char(ch)) { s.push_back((char) ch); m_is.get(); } } m_next = std::move(s); } const std::string &SmtLibTokenizer::peek() { load(false); return *m_next; } std::string SmtLibTokenizer::next() { load(false); std::string s = *std::move(m_next); m_next.reset(); return s; } bool SmtLibTokenizer::has_next() { load(true); return m_next.has_value(); } void SmtLibTokenizer::consume(const std::string &s) { std::stringstream ss(s); SmtLibTokenizer t(ss); t.ignore_whitespace(m_ignore_whitespace); while (t.has_next()) { std::string expected = t.next(); std::string found = next(); if (expected != found) { std::stringstream msg; msg << "SMT-LIB tokenization error: tried to consume \"" << expected << "\" but found \"" << found << "\""; throw std::runtime_error(msg.str()); } } } Model SmtLibParser::get_model(std::istream &is) const { SmtLibTokenizer t(is); t.consume("("); Model m; while (true) { const std::string &tok = t.peek(); if (tok == ")") { break; } else if (tok == ";") { consume_comment(t); } else { parse_function_def(m, t); } } t.consume(")"); assert(t.ignoring_whitespace()); t.ignore_whitespace(false); // Remove EOL t.next(); return m; } void SmtLibParser::consume_comment(SmtLibTokenizer &t) const { t.consume(";;"); assert(t.ignoring_whitespace()); t.ignore_whitespace(false); while (t.next() != "\n") { // Keep cruising } t.ignore_whitespace(true); } void skip_rest_of_s_exp(SmtLibTokenizer &t); std::string parse_identifier(SmtLibTokenizer &t); void SmtLibParser::parse_function_def(Model &m, SmtLibTokenizer &t) const { t.consume("("); auto tok = t.peek(); if (tok == "forall" || tok == "declare") { skip_rest_of_s_exp(t); return; } t.consume("define-fun"); std::string id = parse_identifier(t); // Ignore args t.consume("("); skip_rest_of_s_exp(t); // Ignore type parse_type(t); auto it = m_vars.find(id); if (it != m_vars.end()) { term_ptr x = it->second; if (should_record(x->sym)) { m.emplace(x, parse_term(t, x->sym)); } } skip_rest_of_s_exp(t); } std::string parse_string_raw(SmtLibTokenizer &t) { t.consume("\""); assert(t.ignoring_whitespace()); t.ignore_whitespace(false); std::string s; while (true) { std::string tok = t.next(); if (tok == "\"") { // SMT-LIB uses "" to represent the character " if (t.peek() != "\"") { break; } t.consume(tok); } else if (tok == "\\" && t.peek() == "u") { // Handle unicode (in a hacky way). Not sure if we also need to account for the backslash being escaped. t.consume("u"); t.consume("{"); std::string val; while ((tok = t.next()) != "}") { val += tok; } s.push_back((char) std::stoi(val, nullptr, 16)); continue; } s += tok; } t.ignore_whitespace(true); return s; } bool is_ident_char(int ch) { if (std::isalnum(ch)) { return true; } switch (ch) { case '~': case '!': case '@': case '%': case '^': case '&': case '*': case '_': case '-': case '+': case '=': case '<': case '>': case '.': case '?': case '/': return true; } return false; } std::string parse_identifier(SmtLibTokenizer &t) { std::string s = t.next(); assert(t.ignoring_whitespace()); t.ignore_whitespace(false); if (s == "|") { while (t.peek() != "|") { s += t.next(); } t.consume("|"); } else { while (true) { const std::string &tok = t.peek(); if (is_ident_char(tok[0])) { t.consume(tok); s += tok; } else { if (std::isspace(tok[0])) { t.consume(tok); } break; } } } t.ignore_whitespace(true); return s; } void skip_rest_of_s_exp(SmtLibTokenizer &t) { unsigned depth = 1; while (depth > 0) { const std::string &tok = t.peek(); if (tok == "(") { t.consume(tok); depth++; } else if (tok == ")") { t.consume(tok); depth--; } else if (tok == "\"") { parse_string_raw(t); } else if (tok == "|") { parse_identifier(t); } else { t.consume(tok); } } } void SmtLibParser::parse_type(SmtLibTokenizer &t) const { std::string tok = t.next(); if (tok == "(") { skip_rest_of_s_exp(t); } } bool SmtLibParser::should_record(Symbol sym) const { switch (sym) { /* INSERT 0 */ default: return false; } } term_ptr parse_string(SmtLibTokenizer &t) { return Term::make_moved(parse_string_raw(t)); } uint64_t parse_bv(SmtLibTokenizer &t) { t.consume("#"); std::string tok = t.next(); std::string prefix = tok.substr(0, 1); std::string num = tok.substr(1); int base{0}; if (prefix == "b") { base = 2; } else { assert(prefix == "x"); base = 16; } return std::stoull(num, nullptr, base); } template To bit_cast(From from) { To dst; memcpy(&dst, &from, sizeof(To)); return dst; } term_ptr parse_i32(SmtLibTokenizer &t) { return Term::make(bit_cast(parse_bv(t))); } term_ptr parse_i64(SmtLibTokenizer &t) { return Term::make(bit_cast(parse_bv(t))); } template term_ptr parse_fp(SmtLibTokenizer &t) { t.consume("("); T val; if (t.peek() == "fp") { t.consume("fp"); uint64_t sign = parse_bv(t); uint64_t exp = parse_bv(t); uint64_t mant = parse_bv(t); uint64_t bits = sign << (E + S - 1); bits |= exp << (S - 1); bits |= mant; val = bit_cast(bits); } else { t.consume("_"); std::string next = t.next(); if (next == "NaN") { val = std::numeric_limits::quiet_NaN(); } else if (next == "+") { if (t.peek() == "oo") { t.consume("oo"); val = +std::numeric_limits::infinity(); } else { t.consume("zero"); val = +0.0f; } } else { assert(next == "-"); if (t.peek() == "oo") { t.consume("oo"); val = -std::numeric_limits::infinity(); } else { t.consume("zero"); val = -0.0f; } } } skip_rest_of_s_exp(t); return Term::make(val); } term_ptr parse_fp32(SmtLibTokenizer &t) { return parse_fp(t); } term_ptr parse_fp64(SmtLibTokenizer &t) { return parse_fp(t); } term_ptr parse_bool(SmtLibTokenizer &t) { std::string tok = t.next(); if (tok == "true") { return Term::make(true); } else { assert(tok == "false"); return Term::make(false); } } template term_ptr parse_adt(SmtLibTokenizer &t) { unsigned num_s_exps_to_skip = 0; if (t.peek() == "(") { t.consume("("); num_s_exps_to_skip++; if (t.peek() == "as") { t.consume("as"); if (t.peek() == "(") { t.consume("("); num_s_exps_to_skip++; } } } Fn fn; term_ptr term = fn(t); for (unsigned i = 0; i < num_s_exps_to_skip; ++i) { skip_rest_of_s_exp(t); } return term; } /* INSERT 1 */ term_ptr SmtLibParser::parse_term(SmtLibTokenizer &t, Symbol sym) const { switch (sym) { /* INSERT 2 */ default: abort(); } } } ================================================ FILE: src/main/resources/codegen/src/smt_parser.hpp ================================================ // // Created by Aaron Bembenek on 1/23/23. // #ifndef CODEGEN_SMT_PARSER_HPP #define CODEGEN_SMT_PARSER_HPP #include #include #include "Term.hpp" namespace flg { class SmtLibTokenizer { public: explicit SmtLibTokenizer(std::istream &is) : m_is(is) {} void ignore_whitespace(bool ignore) { m_ignore_whitespace = ignore; } bool ignoring_whitespace() { return m_ignore_whitespace; } const std::string &peek(); std::string next(); bool has_next(); void consume(const std::string &s); private: std::istream &m_is; bool m_ignore_whitespace{true}; std::optional m_next{}; void load(bool allow_eof); static bool is_word_char(int ch) { return ch == '_' || std::isalnum(ch); } }; class SmtLibParser { public: explicit SmtLibParser(std::unordered_map &vars) : m_vars(vars) {} Model get_model(std::istream &is) const; private: const std::unordered_map &m_vars; void consume_comment(SmtLibTokenizer &t) const; void parse_function_def(Model &m, SmtLibTokenizer &t) const; void parse_type(SmtLibTokenizer &t) const; bool should_record(Symbol sym) const; term_ptr parse_term(SmtLibTokenizer &t, Symbol sym) const; }; } #endif //CODEGEN_SMT_PARSER_HPP ================================================ FILE: src/main/resources/codegen/src/smt_shim.cpp ================================================ // // Created by Aaron Bembenek on 8/9/22. // #include "globals.h" #include "smt_shim.h" #include "smt_parser.hpp" #include #include #include #include namespace flg::smt { static const auto declarations = R"_( /* INSERT 0 */ )_"; bool is_solver_var(term_ptr t) { switch (t->sym) { /* INSERT 1 */ default: return false; } } bool needs_type_annotation(Symbol sym) { switch (sym) { /* INSERT 2 */ default: return false; } } class MyTypeInferer { public: std::deque go(term_ptr t); private: std::deque m_annotations; std::vector> m_constraints; TypeSubst m_subst; Type visit(term_ptr t); void unify_constraints(); void unify(const Type &ty1, const Type &ty2); }; std::deque MyTypeInferer::go(term_ptr t) { m_constraints.clear(); m_subst.clear(); m_annotations.clear(); visit(t); unify_constraints(); for (auto &type: m_annotations) { type = m_subst.apply(type); } return m_annotations; } Type MyTypeInferer::visit(term_ptr t) { std::vector types; functor_type ft = Type::lookup(t->sym); if (needs_type_annotation(t->sym)) { m_annotations.push_back(ft.second); } if (!ft.first.empty()) { auto &x = t->as_complex(); if (!is_solver_var(t)) { for (size_t i = 0; i < x.arity; ++i) { m_constraints.emplace_back(visit(x.val[i]), ft.first[i]); } } } return ft.second; } void MyTypeInferer::unify_constraints() { while (!m_constraints.empty()) { auto constraint = m_constraints.back(); m_constraints.pop_back(); auto ty1 = m_subst.apply(constraint.first); auto ty2 = m_subst.apply(constraint.second); if (ty1.is_var) { m_subst.put(ty1, ty2); } else if (ty2.is_var) { m_subst.put(ty2, ty1); } else { unify(ty1, ty2); } } } void MyTypeInferer::unify(const Type &ty1, const Type &ty2) { assert(ty1.name == ty2.name); auto args1 = ty1.args; auto args2 = ty2.args; for (auto it1 = args1.begin(), it2 = args2.begin(); it1 != args1.end(); it1++, it2++) { m_constraints.emplace_back(*it1, *it2); } } SmtLibShim::SmtLibShim(boost::process::child &&proc, boost::process::opstream &&in, boost::process::ipstream &&out) : m_proc{std::move(proc)}, m_in{std::move(in)}, m_out{std::move(out)} { s_shims[this] = true; } void SmtLibShim::set_timeout(int32_t timeout) { if (timeout < 0) { cerr << "Warning: ignoring negative timeout provided to SMT" << endl; timeout = numeric_limits::max(); } m_in << "(set-option :timeout " << timeout << ")\n"; } void SmtLibShim::declare_vars(term_ptr t) { if (is_solver_var(t)) { if (m_solver_vars.find(t) == m_solver_vars.end()) { std::stringstream ss; ss << "x" << t->intize(); auto s = ss.str(); m_solver_vars.emplace(t, s); m_solver_var_lookup.emplace(s, t); m_solver_vars_in_order.push_back(t); m_in << "(declare-fun " << s << " () " << Type::lookup(t->sym).second << ")\n"; } return; } switch (t->sym) { case Symbol::boxed_bool: case Symbol::boxed_i32: case Symbol::boxed_i64: case Symbol::boxed_fp32: case Symbol::boxed_fp64: case Symbol::boxed_string: break; default: auto &x = t->as_complex(); for (size_t i = 0; i < x.arity; ++i) { declare_vars(x.val[i]); } } } void SmtLibShim::make_declarations() { m_in << declarations << "\n"; } void SmtLibShim::push() { // Avoid push timing out. See set_timeout(numeric_limits::max()); m_in << "(push 1)\n"; m_stack_positions.push_back(m_solver_vars_in_order.size()); } void SmtLibShim::pop(unsigned int n) { m_in << "(pop " << n << ")\n"; if (n > 0) { unsigned int stack_pos = m_stack_positions.size() - n; unsigned int start_pos = m_stack_positions[stack_pos]; m_stack_positions.erase(m_stack_positions.begin() + stack_pos, m_stack_positions.end()); unsigned int how_many = m_solver_vars_in_order.size() - start_pos; for (unsigned int i = 0; i < how_many; ++i) { auto var = m_solver_vars_in_order.back(); auto symbol = m_solver_vars[var]; m_solver_vars.erase(var); m_solver_var_lookup.erase(symbol); m_solver_vars_in_order.pop_back(); } } } void SmtLibShim::make_assertion(term_ptr assertion) { declare_vars(assertion); m_annotations = MyTypeInferer().go(assertion); m_in << "(assert "; serialize(assertion); m_in << ")\n"; assert(m_annotations.empty()); } SmtStatus SmtLibShim::check_sat_assuming(const std::vector &onVars, const std::vector &offVars, int timeout) { set_timeout(timeout); if (onVars.empty() && offVars.empty()) { m_in << "(check-sat)\n"; } else { m_in << "(check-sat-assuming ("; for (auto var: onVars) { m_in << lookup_var(var) << " "; } for (auto var: offVars) { m_in << "(not " << lookup_var(var) << ") "; } m_in << "))\n"; } m_in.flush(); string line; auto op = [&] { getline(m_out, line); }; if (globals::smt_stats) { globals::smt_time.local() += time(op); } else { op(); } SmtStatus status; if (line == "sat") { status = SmtStatus::sat; } else if (line == "unsat") { status = SmtStatus::unsat; } else if (line == "unknown") { status = SmtStatus::unknown; } else { cerr << "Unexpected SMT response:" << endl; cerr << line << endl; abort(); } return status; } Model SmtLibShim::get_model() { m_in << "(get-model)\n"; m_in.flush(); SmtLibParser parser(m_solver_var_lookup); return parser.get_model(m_out); } void SmtLibShim::serialize(term_ptr t) { switch (t->sym) { case Symbol::boxed_bool: { m_in << *t; break; } case Symbol::boxed_string: { m_in << "\"" << std::regex_replace(t->as_base().val, std::regex("\""), "\"\"") << "\""; break; } case Symbol::boxed_i32: { m_in << "#x" << boost::format{"%08x"} % t->as_base().val; break; } case Symbol::boxed_i64: { m_in << "#x" << boost::format{"%016x"} % t->as_base().val; break; } case Symbol::boxed_fp32: { serialize_fp(t); break; } case Symbol::boxed_fp64: { serialize_fp(t); break; } /* INSERT 3 */ default: auto &x = t->as_complex(); stringstream ss; serialize(serialize_sym(x.sym), x); } } void SmtLibShim::serialize(const std::string &repr, const ComplexTerm &t) { size_t n = t.arity; if (n > 0) { m_in << "("; } if (needs_type_annotation(t.sym)) { m_in << "(as " << repr << " " << next_annotation() << ")"; } else { m_in << repr; } for (size_t i = 0; i < n; ++i) { m_in << " "; serialize(t.val[i]); } if (n > 0) { m_in << ")"; } } template void SmtLibShim::serialize_bit_string(T val) { m_in << "#b" << std::bitset(val).to_string(); } template void SmtLibShim::serialize_bv_to_bv(term_ptr t) { auto arg = arg0(t); if (From < To) { m_in << "((_ " << (Signed ? "sign" : "zero") << "_extend " << (To - From) << ") "; serialize(arg); m_in << ")"; } else if (From > To) { m_in << "((_ extract " << (To - 1) << " 0) "; serialize(arg); m_in << ")"; } else { serialize(arg); } } void SmtLibShim::serialize_bv_extract(term_ptr t) { m_in << "((_ extract " << *argn(t, 2) << " " << *argn(t, 1) << ") "; serialize(arg0(t)); m_in << ")"; } template void SmtLibShim::serialize_bv_to_fp(term_ptr t) { m_in << "((_ to_fp " << E << " " << S << ") RNE "; serialize(arg0(t)); m_in << ")"; } template void SmtLibShim::serialize_fp(term_ptr t) { auto val = t->as_base().val; stringstream ss; ss << E << " " << S; auto s = ss.str(); if (isnan(val)) { m_in << "(_ NaN " << s << ")"; } else if (isinf(val)) { if (val > 0) { m_in << "(_ +oo " << s << ")"; } else { m_in << "(_ -oo " << s << ")"; } } else { m_in << "((_ to_fp " << s << ") RNE " << val << ")"; } } template void SmtLibShim::serialize_fp_to_bv(term_ptr t) { m_in << "((_ " << (Signed ? "fp.to_sbv" : "fp.to_ubv") << " " << N << ") RNE "; serialize(arg0(t)); m_in << ")"; } template void SmtLibShim::serialize_fp_to_fp(term_ptr t) { m_in << "((_ to_fp " << E << " " << S << ") RNE "; serialize(arg0(t)); m_in << ")"; } void SmtLibShim::serialize_let(term_ptr t) { auto &x = t->as_complex(); m_in << "(let (("; serialize(x.val[0]); m_in << " "; serialize(x.val[1]); m_in << ")) "; serialize(x.val[2]); m_in << ")"; } template void SmtLibShim::serialize_int(term_ptr t) { m_in << arg0(t)->as_base().val; } template void SmtLibShim::serialize_int2bv(term_ptr t) { m_in << "((_ int2bv " << W << ") "; serialize(arg0(t)); m_in << ")"; } template void SmtLibShim::serialize_quantifier(term_ptr t) { auto &x = t->as_complex(); m_in << "(" << (Exists ? "exists (" : "forall ("); for (auto &v: Term::vectorize_list_term(x.val[0])) { // Consume annotation for cons next_annotation(); auto var = arg0(v); m_in << "("; serialize(var); m_in << " " << Type::lookup(var->sym).second << ")"; } m_in << ") "; // Consume annotation for nil next_annotation(); auto pats = Term::vectorize_list_term(x.val[2]); if (!pats.empty()) { m_in << "(! "; } serialize(x.val[1]); if (!pats.empty()) { for (auto &pat: pats) { m_in << " :pattern ("; // Consume annotation for cons next_annotation(); bool first{true}; for (auto &sub: Term::vectorize_list_term(pat)) { if (!first) { m_in << " "; } first = false; // Consume annotation for cons next_annotation(); serialize(arg0(sub)); } m_in << ")"; // Consume annotation for nil next_annotation(); } m_in << ")"; } // Consume annotation for nil next_annotation(); m_in << ")"; } string SmtLibShim::serialize_sym(Symbol sym) { switch (sym) { /* INSERT 4 */ default: stringstream ss; ss << "|" << sym << "|"; return ss.str(); } } string SmtLibShim::serialize_tester(Symbol sym) { stringstream ss; ss << sym; string s = ss.str().substr(4, string::npos); return "|is-" + s + "|"; } } ================================================ FILE: src/main/resources/codegen/src/smt_shim.h ================================================ // // Created by Aaron Bembenek on 8/9/22. // #ifndef CODEGEN_SMT_SHIM_H #define CODEGEN_SMT_SHIM_H #include #include #include #include "Term.hpp" #include "Type.hpp" namespace flg::smt { enum class SmtStatus { sat, unsat, unknown }; class SmtShim { public: NO_COPY_OR_ASSIGN(SmtShim); SmtShim() = default; virtual void make_declarations() = 0; virtual void make_assertion(term_ptr assertion) = 0; virtual void push() = 0; virtual void pop(unsigned int n) = 0; void pop() { pop(1); } virtual SmtStatus check_sat_assuming(const std::vector &onVars, const std::vector &offVars, int32_t timeout) = 0; virtual Model get_model() = 0; virtual ~SmtShim() = default; }; class SmtLibShim : public SmtShim { public: NO_COPY_OR_ASSIGN(SmtLibShim); SmtLibShim(boost::process::child &&proc, boost::process::opstream &&in, boost::process::ipstream &&out); void make_declarations() override; void make_assertion(term_ptr assertion) override; void push() override; void pop(unsigned int n) override; SmtStatus check_sat_assuming(const std::vector &onVars, const std::vector &offVars, int32_t timeout) override; Model get_model() override; static void terminate_all_children() { for (auto p : s_shims) { if (p.second) { p.first->m_in.close(); p.first->m_proc.terminate(); } } } ~SmtLibShim() override { s_shims[this] = false; } private: inline static tbb::concurrent_unordered_map s_shims; class Logger { public: explicit Logger(boost::process::opstream &&in) : m_in{std::move(in)} {} template Logger &operator<<(const T &val) { m_in << val; /* std::cerr << val; std::cerr.flush(); */ return *this; } void flush() { m_in.flush(); /* std::cerr.flush(); */ } void close() { m_in.close(); } private: boost::process::opstream m_in; }; boost::process::child m_proc; Logger m_in; boost::process::ipstream m_out; std::unordered_map m_solver_vars; std::unordered_map m_solver_var_lookup; std::vector m_solver_vars_in_order; std::vector m_stack_positions; std::deque m_annotations; void set_timeout(int32_t timeout); Type next_annotation() { auto ty = m_annotations.front(); m_annotations.pop_front(); return ty; } void declare_vars(term_ptr t); std::string lookup_var(term_ptr var) { return m_solver_vars.find(var)->second; } void serialize(term_ptr t); static term_ptr argn(term_ptr t, size_t n) { return t->as_complex().val[n]; } static term_ptr arg0(term_ptr t) { return argn(t, 0); } void serialize(const std::string &repr, const ComplexTerm &t); template void serialize_bit_string(T val); template void serialize_bv_to_bv(term_ptr t); void serialize_bv_extract(term_ptr t); template void serialize_bv_to_fp(term_ptr t); template void serialize_fp(term_ptr t); template void serialize_fp_to_bv(term_ptr t); template void serialize_fp_to_fp(term_ptr t); void serialize_let(term_ptr t); template void serialize_int(term_ptr t); template void serialize_quantifier(term_ptr t); std::string serialize_sym(Symbol sym); std::string serialize_tester(Symbol sym); template void serialize_int2bv(term_ptr t); }; } #endif //CODEGEN_SMT_SHIM_H ================================================ FILE: src/main/resources/codegen/src/smt_solver.cpp ================================================ // // Created by Aaron Bembenek on 8/9/22. // #include "globals.h" #include "smt_solver.h" #include namespace flg::smt { void break_into_conjuncts(term_ptr t, std::vector &acc); void break_into_conjuncts_negated(term_ptr t, std::vector &acc); std::vector break_into_conjuncts(term_ptr t) { std::vector conjuncts; break_into_conjuncts(t, conjuncts); return conjuncts; } TopLevelSmtSolver::TopLevelSmtSolver() { std::unique_ptr inner; switch (globals::smt_solver_mode) { case SmtSolverMode::naive: inner = std::make_unique(make_shim()); break; case SmtSolverMode::push_pop: inner = std::make_unique(make_shim()); break; case SmtSolverMode::check_sat_assuming: inner = std::make_unique(make_shim()); if (globals::smt_double_check) { auto checker = std::make_unique(make_shim()); inner = std::make_unique(std::move(inner), std::move(checker)); } break; } m_delegate = std::make_unique(std::move(inner)); } std::unique_ptr TopLevelSmtSolver::make_shim() { namespace bp = boost::process; // Force synchronization here to avoid some issues that come up with the // `bp::child` constructor and pipes in a multithreaded setting. See // static std::mutex mtx; std::lock_guard guard(mtx); bp::ipstream out; bp::opstream in; bp::child proc("z3 -in", bp::std_in < in, (bp::std_out & bp::std_err) > out); return std::make_unique(std::move(proc), std::move(in), std::move(out)); } SmtResult TopLevelSmtSolver::check(const std::vector &assertions, bool get_model, int timeout) { return m_delegate->check(assertions, get_model, timeout); } SmtResult TopLevelSmtSolver::check(term_ptr assertion) { return check(break_into_conjuncts(assertion), false, std::numeric_limits::max()); } MemoizingSmtSolver::MemoizingSmtSolver(std::unique_ptr &&delegate) : m_delegate{std::move(delegate)} {} SmtResult MemoizingSmtSolver::check(const std::vector &assertions, bool get_model, int timeout) { if (timeout < 0) { timeout = -1; } if (assertions.empty()) { auto model = get_model ? std::optional{} : std::nullopt; return {SmtStatus::sat, model}; } std::set set(assertions.begin(), assertions.end()); if (set.find(Term::make(false)) != set.end()) { return {SmtStatus::unsat, {}}; } SmtResult res; std::shared_future fut; memo_key_t key{std::move(set), get_model, timeout}; auto it = s_memo.find(key); if (it == s_memo.end()) { std::promise p; fut = p.get_future(); auto it2 = s_memo.emplace(std::move(key), fut); if (it2.second) { res = m_delegate->check(assertions, get_model, timeout); p.set_value(res); } else { fut = it2.first->second; auto op = [&] { res = fut.get(); }; if (globals::smt_stats) { globals::smt_wait_time.local() += time(op); } else { op(); } } } else { fut = it->second; auto op = [&] { res = fut.get(); }; if (globals::smt_stats) { globals::smt_wait_time.local() += time(op); } else { op(); } } return res; } SmtResult AbstractSmtSolver::check(const std::vector &assertions, bool get_model, int timeout) { if (!m_initialized) { initialize(); m_initialized = true; } term_vector_pair p = make_assertions(assertions); auto status = m_shim->check_sat_assuming(p.first, p.second, timeout); SmtResult res{status, {}}; if (status == SmtStatus::sat && get_model) { res.model = m_shim->get_model(); } cleanup(); return res; } void PushPopNaiveSolver::initialize() { m_shim->make_declarations(); } AbstractSmtSolver::term_vector_pair PushPopNaiveSolver::make_assertions(const std::vector &assertions) { m_shim->push(); for (auto assertion: assertions) { m_shim->make_assertion(assertion); } if (globals::smt_stats) { globals::smt_cache_clears.local()++; globals::smt_cache_misses.local() += assertions.size(); } return {{}, {}}; } void PushPopNaiveSolver::cleanup() { m_shim->pop(); } void CheckSatAssumingSolver::initialize() { m_shim->make_declarations(); m_shim->push(); } AbstractSmtSolver::term_vector_pair CheckSatAssumingSolver::make_assertions(const std::vector &assertions) { std::vector on_vars; unsigned hits{0}; unsigned misses{0}; for (auto assertion: assertions) { auto &var = m_conjuncts_to_vars[assertion]; if (!var) { var = ComplexTerm::fresh_smt_var(); misses++; } else { hits++; } on_vars.emplace_back(var); #ifdef FLG_DEV auto imp = nullptr; #else auto imp = Term::make(var, assertion); #endif m_shim->make_assertion(imp); } if (globals::smt_stats) { if (hits) { globals::smt_cache_hits.local() += hits; } if (misses) { globals::smt_cache_misses.local() += misses; } } return {on_vars, {}}; } void CheckSatAssumingSolver::cleanup() { if (m_conjuncts_to_vars.size() > globals::smt_cache_size) { m_conjuncts_to_vars.clear(); m_shim->pop(); m_shim->push(); if (globals::smt_stats) { globals::smt_cache_clears.local()++; } } } void PushPopSolver::initialize() { m_shim->make_declarations(); } AbstractSmtSolver::term_vector_pair PushPopSolver::make_assertions(const std::vector &assertions) { unsigned int pos = find_diff_pos(assertions); if (globals::smt_stats) { if (pos) { globals::smt_cache_hits.local() += pos; } else if (!m_stack.empty()) { globals::smt_cache_clears.local()++; } auto misses = assertions.size() - pos; if (misses) { globals::smt_cache_misses.local() += misses; } } shrink_cache(m_stack.size() - pos); assert(m_stack.size() == pos); assert(pos <= assertions.size()); for (auto it = assertions.begin() + pos; it != assertions.end(); ++it) { auto elt = *it; m_shim->push(); m_shim->make_assertion(elt); m_stack.push_back(elt); } assert(m_stack.size() <= assertions.size()); return {{}, {}}; } void PushPopSolver::cleanup() { // do nothing } unsigned int PushPopSolver::find_diff_pos(const std::vector &assertions) { unsigned int pos = 0; unsigned int end = std::min(m_stack.size(), assertions.size()); while (pos < end) { if (m_stack[pos] != assertions[pos]) { break; } pos++; } return pos; } void PushPopSolver::shrink_cache(unsigned int num_to_pop) { m_shim->pop(num_to_pop); for (unsigned int i = 0; i < num_to_pop; ++i) { m_stack.pop_back(); } } SmtResult DoubleCheckingSolver::check(const std::vector &assertions, bool get_model, int timeout) { auto res = m_first->check(assertions, get_model, timeout); if (res.status == SmtStatus::unknown) { res = m_second->check(assertions, get_model, timeout); } return res; } void break_into_conjuncts_negated(term_ptr t, std::vector &acc) { if (t->sym == Symbol::smt_not) { auto &c = t->as_complex(); // Turn ~~A into A break_into_conjuncts(c.val[0], acc); } else if (t->sym == Symbol::smt_imp) { // Turn ~(A => B) into A /\ ~B auto &c = t->as_complex(); break_into_conjuncts(c.val[0], acc); break_into_conjuncts_negated(c.val[1], acc); } else if (t->sym == Symbol::smt_or) { // Turn ~(A \/ B) into ~A /\ ~B auto &c = t->as_complex(); break_into_conjuncts_negated(c.val[0], acc); break_into_conjuncts_negated(c.val[1], acc); } else if (t == Term::make(true)) { // Turn ~True into False acc.push_back(Term::make(false)); } else if (t != Term::make(false)) { #ifndef FLG_DEV t = Term::make(t); #endif acc.push_back(t); } } void break_into_conjuncts(term_ptr t, std::vector &acc) { if (t->sym == Symbol::smt_and) { auto &c = t->as_complex(); break_into_conjuncts(c.val[0], acc); break_into_conjuncts(c.val[1], acc); } else if (t->sym == Symbol::smt_not) { auto &c = t->as_complex(); break_into_conjuncts_negated(c.val[0], acc); } else if (t != Term::make(true)) { acc.push_back(t); } } } ================================================ FILE: src/main/resources/codegen/src/smt_solver.h ================================================ // // Created by Aaron Bembenek on 8/9/22. // #ifndef CODEGEN_SMT_SOLVER_H #define CODEGEN_SMT_SOLVER_H #include "smt_shim.h" #include #include #include #ifdef FLG_EAGER_EVAL #include #endif namespace flg::smt { enum class SmtSolverMode { naive, push_pop, check_sat_assuming }; struct SmtResult { SmtStatus status; std::optional model; }; class SmtSolver { public: NO_COPY_OR_ASSIGN(SmtSolver); SmtSolver() = default; virtual SmtResult check(const std::vector &assertions, bool get_model, int32_t timeout) = 0; virtual ~SmtSolver() = default; }; class TopLevelSmtSolver : public SmtSolver { public: NO_COPY_OR_ASSIGN(TopLevelSmtSolver); TopLevelSmtSolver(); SmtResult check(const std::vector &assertions, bool get_model, int32_t timeout) override; SmtResult check(term_ptr assertion); private: std::unique_ptr m_delegate; static std::unique_ptr make_shim(); }; #ifdef FLG_EAGER_EVAL class TBBTopLevelSmtSolver : public SmtSolver { public: NO_COPY_OR_ASSIGN(TBBTopLevelSmtSolver); TBBTopLevelSmtSolver() = default; SmtResult check(const std::vector &assertions, bool get_model, int timeout) override { return solvers.local().check(assertions, get_model, timeout); } SmtResult check(term_ptr assertion) { return solvers.local().check(assertion); } private: oneapi::tbb::enumerable_thread_specific solvers; }; #endif class MemoizingSmtSolver : public SmtSolver { public: NO_COPY_OR_ASSIGN(MemoizingSmtSolver); explicit MemoizingSmtSolver(std::unique_ptr &&delegate); SmtResult check(const std::vector &assertions, bool get_model, int32_t timeout) override; private: std::unique_ptr m_delegate; typedef std::tuple, bool, int32_t> memo_key_t; static inline ConcurrentHashMap, boost::hash> s_memo; }; class AbstractSmtSolver : public SmtSolver { public: NO_COPY_OR_ASSIGN(AbstractSmtSolver); explicit AbstractSmtSolver(std::unique_ptr &&shim) : m_shim{std::move(shim)} {} using term_vector_pair = std::pair, std::vector>; SmtResult check(const std::vector &assertions, bool get_model, int32_t timeout) override; protected: virtual void initialize() = 0; virtual term_vector_pair make_assertions(const std::vector &assertions) = 0; virtual void cleanup() = 0; std::unique_ptr m_shim; private: bool m_initialized{false}; }; class PushPopNaiveSolver : public AbstractSmtSolver { public: NO_COPY_OR_ASSIGN(PushPopNaiveSolver); explicit PushPopNaiveSolver(std::unique_ptr &&shim) : AbstractSmtSolver{std::move(shim)} {} private: void initialize() override; term_vector_pair make_assertions(const std::vector &assertions) override; void cleanup() override; }; class CheckSatAssumingSolver : public AbstractSmtSolver { public: NO_COPY_OR_ASSIGN(CheckSatAssumingSolver); explicit CheckSatAssumingSolver(std::unique_ptr &&shim) : AbstractSmtSolver{std::move(shim)} {} private: std::unordered_map m_conjuncts_to_vars; void initialize() override; term_vector_pair make_assertions(const std::vector &assertions) override; void cleanup() override; }; class PushPopSolver : public AbstractSmtSolver { public: NO_COPY_OR_ASSIGN(PushPopSolver); explicit PushPopSolver(std::unique_ptr &&shim) : AbstractSmtSolver{std::move(shim)} {} private: std::vector m_stack; void initialize() override; term_vector_pair make_assertions(const std::vector &assertions) override; void cleanup() override; unsigned int find_diff_pos(const std::vector &assertions); void shrink_cache(unsigned int num_to_pop); }; class DoubleCheckingSolver : public SmtSolver { public: NO_COPY_OR_ASSIGN(DoubleCheckingSolver); DoubleCheckingSolver(std::unique_ptr &&first, std::unique_ptr &&second) : m_first( std::move(first)), m_second(std::move(second)) {} SmtResult check(const std::vector &assertions, bool get_model, int32_t timeout) override; private: std::unique_ptr m_first; std::unique_ptr m_second; }; #if FLG_EAGER_EVAL inline TBBTopLevelSmtSolver smt_solver; #else extern inline TopLevelSmtSolver smt_solver; #if defined(_OPENMP) #pragma omp threadprivate(smt_solver) #endif TopLevelSmtSolver smt_solver; #endif } namespace std { inline std::istream &operator>>(std::istream &in, flg::smt::SmtSolverMode &mode) { std::string token; in >> token; if (token == "naive") { mode = flg::smt::SmtSolverMode::naive; } else if (token == "push-pop") { mode = flg::smt::SmtSolverMode::push_pop; } else if (token == "check-sat-assuming") { mode = flg::smt::SmtSolverMode::check_sat_assuming; } else { throw boost::program_options::validation_error( boost::program_options::validation_error::kind_t::invalid_option_value); } return in; } inline std::ostream &operator<<(std::ostream &out, const flg::smt::SmtSolverMode &mode) { switch (mode) { case flg::smt::SmtSolverMode::naive: out << "naive"; break; case flg::smt::SmtSolverMode::push_pop: out << "push-pop"; break; case flg::smt::SmtSolverMode::check_sat_assuming: out << "check-sat-assuming"; break; } return out; } } #endif //CODEGEN_SMT_SOLVER_H ================================================ FILE: src/main/resources/codegen/src/time.hpp ================================================ // // Created by Aaron Bembenek on 5/20/23. // #pragma once #include namespace flg { typedef std::chrono::duration time_t; template time_t time(const F &f) { auto start = std::chrono::steady_clock::now(); f(); auto end = std::chrono::steady_clock::now(); return std::chrono::duration_cast(end - start); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/CodeGenTester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import static org.junit.Assert.fail; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.Main; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.eval.Tester; import edu.harvard.seas.pl.formulog.magic.MagicSetTransformer; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.util.Util; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class CodeGenTester implements Tester { private List inputDirs = Collections.emptyList(); private final boolean eagerEval; public CodeGenTester(boolean eagerEval) { this.eagerEval = eagerEval; } @Override public void test(String file, List inputDirs) { boolean isBad = file.matches("test\\d\\d\\d_bd.flg"); Path topPath = null; try { InputStream is = getClass().getClassLoader().getResourceAsStream(file); if (is == null) { throw new FileNotFoundException(file + " not found"); } List dirs = new ArrayList<>(); for (String inputDir : inputDirs) { URL dir = getClass().getClassLoader().getResource(inputDir); dirs.add(Paths.get(dir.toURI())); } BasicProgram prog = new Parser().parse(new InputStreamReader(is), dirs); WellTypedProgram wtp = new TypeChecker(prog).typeCheck(); MagicSetTransformer mst = new MagicSetTransformer(wtp); BasicProgram magicProg = mst.transform(true, true); String name = file.substring(0, file.indexOf('.')); topPath = Path.of("codegen", "tests", name); boolean ok = evaluate(name, topPath, magicProg); if (!ok && !isBad) { String msg = "Test failed for a good program"; fail(msg); } if (ok && isBad) { fail("Test succeeded for a bad program"); } } catch (Exception e) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); out.println("Unexpected exception:"); e.printStackTrace(out); fail(baos.toString()); } finally { if (!Configuration.keepCodeGenTestDirs && topPath != null) { Util.clean(topPath.toFile(), true); } } } private boolean evaluate(String name, Path topPath, BasicProgram prog) throws Exception { File srcDir = topPath.resolve("src").toFile(); srcDir.mkdirs(); Util.clean(srcDir, false); Path buildPath = topPath.resolve("build"); CodeGen cg = new CodeGen(prog, topPath.toFile()); cg.go(); var cmakeCmds = new ArrayList<>( Arrays.asList( "cmake", "-B", buildPath.toString(), "-S", topPath.toString(), "-DCMAKE_BUILD_TYPE=Debug")); if (eagerEval) { cmakeCmds.add("-DFLG_EAGER_EVAL=On"); } if (Configuration.cxxCompiler != null) { cmakeCmds.add("-DCMAKE_CXX_COMPILER=" + Configuration.cxxCompiler); } Process cmake = Runtime.getRuntime().exec(cmakeCmds.toArray(new String[0])); if (cmake.waitFor() != 0) { System.err.println("Could not cmake test"); printToStdErr(cmake.getErrorStream()); return false; } Process make = Runtime.getRuntime() .exec( new String[] { "cmake", "--build", buildPath.toString(), "-j", String.valueOf(Main.parallelism) }); if (make.waitFor() != 0) { System.err.println("Could not make test"); printToStdErr(make.getErrorStream()); return false; } String cmd = topPath.resolve("build").resolve("flg").toString(); for (String inputDir : inputDirs) { Path p = Paths.get(getClass().getClassLoader().getResource(inputDir).toURI()); cmd += " --fact-dir " + p; } cmd += " --dump-sizes -j 4"; Process flg = Runtime.getRuntime().exec(cmd); if (flg.waitFor() != 0) { System.err.println("Evaluation error"); printToStdErr(flg.getErrorStream()); return false; } BufferedReader br = new BufferedReader(new InputStreamReader(flg.getInputStream())); String line; while ((line = br.readLine()) != null) { if (line.equals("ok: 1") || line.equals("query__ok: 1")) { return true; } } return false; } private static void printToStdErr(InputStream is) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while ((line = br.readLine()) != null) { System.err.println(line); } } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/CompiledEagerEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2024 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; /*- * #%L * Formulog * %% * Copyright (C) 2018 - 2020 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.eval.CommonEvaluationTest; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; public class CompiledEagerEvaluationTest extends CommonEvaluationTest { static { if (!Configuration.testCodeGenEager) { System.err.println( "WARNING: skipping CompiledEagerEvaluationTest; enable with system property" + " `-DtestCodeGenEager`"); } } public CompiledEagerEvaluationTest() { super(Configuration.testCodeGenEager ? new CodeGenTester(true) : new NopTester<>()); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/CompiledEagerMagicSetTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2024 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; /*- * #%L * Formulog * %% * Copyright (C) 2018 - 2020 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; import edu.harvard.seas.pl.formulog.magic.CommonMagicSetTest; public class CompiledEagerMagicSetTest extends CommonMagicSetTest { static { if (!Configuration.testCodeGenEager) { System.err.println( "WARNING: skipping CompiledEagerMagicSetTest; enable with system property" + " `-DtestCodeGenEager`"); } } public CompiledEagerMagicSetTest() { super(Configuration.testCodeGenEager ? new CodeGenTester(true) : new NopTester<>()); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/CompiledSemiNaiveEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.eval.CommonEvaluationTest; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; public class CompiledSemiNaiveEvaluationTest extends CommonEvaluationTest { static { if (!Configuration.testCodeGen) { System.err.println( "WARNING: skipping CompiledSemiNaiveEvaluationTest; enable with system property" + " `-DtestCodeGen`"); } } public CompiledSemiNaiveEvaluationTest() { super(Configuration.testCodeGen ? new CodeGenTester(false) : new NopTester<>()); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/CompiledSemiNaiveMagicSetTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.Configuration; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; import edu.harvard.seas.pl.formulog.magic.CommonMagicSetTest; public class CompiledSemiNaiveMagicSetTest extends CommonMagicSetTest { static { if (!Configuration.testCodeGen) { System.err.println( "WARNING: skipping CompiledSemiNaiveMagicSetTest; enable with system property" + " `-DtestCodeGen`"); } } public CompiledSemiNaiveMagicSetTest() { super(Configuration.testCodeGen ? new CodeGenTester(false) : new NopTester<>()); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/codegen/NopTester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.codegen; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.Tester; import java.util.List; public class NopTester implements Tester { @Override public void test(String file, List inputDirs) { // do nothing } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/AbstractEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import java.util.Collections; import java.util.List; public abstract class AbstractEvaluationTest { private final Tester tester; public AbstractEvaluationTest(Tester tester) { this.tester = tester; } protected void test(String file, List inputDirs) { tester.test(file, inputDirs); } protected void test(String file) { test(file, Collections.singletonList("")); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/AbstractTester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import static org.junit.Assert.fail; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public abstract class AbstractTester implements Tester { @Override public void test(String file, List inputDirs) { boolean isBad = file.matches("test\\d\\d\\d_bd.flg"); try { InputStream is = getClass().getClassLoader().getResourceAsStream(file); if (is == null) { throw new FileNotFoundException(file + " not found"); } List dirs = new ArrayList<>(); for (String inputDir : inputDirs) { URL dir = getClass().getClassLoader().getResource(inputDir); dirs.add(Paths.get(dir.toURI())); } BasicProgram prog = new Parser().parse(new InputStreamReader(is), dirs); WellTypedProgram wellTypedProg = (new TypeChecker(prog)).typeCheck(); T eval = setup(wellTypedProg); boolean ok = evaluate(eval); if (!ok && !isBad) { String msg = "Test failed for a good program"; fail(msg); } if (ok && isBad) { fail("Test succeeded for a bad program"); } } catch (Exception e) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); out.println("Unexpected exception:"); e.printStackTrace(out); fail(baos.toString()); } } protected abstract T setup(WellTypedProgram prog) throws InvalidProgramException, EvaluationException; protected abstract boolean evaluate(T eval) throws EvaluationException; } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/CommonEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2024 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import java.util.Arrays; import org.junit.Test; public abstract class CommonEvaluationTest extends AbstractEvaluationTest { public CommonEvaluationTest(Tester tester) { super(tester); } @Test public void test018() { test("test018_ok.flg"); } @Test public void test019() { test("test019_ok.flg"); } @Test public void test020() { test("test020_ok.flg"); } @Test public void test021() { test("test021_ok.flg"); } @Test public void test022() { test("test022_ok.flg"); } @Test public void test023() { test("test023_ok.flg"); } @Test public void test024() { test("test024_ok.flg"); } @Test public void test027() { test("test027_ok.flg"); } @Test public void test029() { test("test029_ok.flg"); } @Test public void test030() { test("test030_ok.flg"); } @Test public void test032() { test("test032_ok.flg"); } @Test public void test033() { test("test033_ok.flg"); } @Test public void test034() { test("test034_ok.flg"); } @Test public void test035() { test("test035_ok.flg"); } @Test public void test037() { test("test037_ok.flg"); } @Test public void test038() { test("test038_ok.flg"); } @Test public void test039() { test("test039_ok.flg"); } @Test public void test040() { test("test040_ok.flg"); } @Test public void test041() { test("test041_ok.flg"); } @Test public void test043() { test("test043_ok.flg"); } @Test public void test044() { test("test044_ok.flg"); } @Test public void test045() { test("test045_ok.flg"); } @Test public void test046() { test("test046_ok.flg"); } @Test public void test047() { test("test047_ok.flg"); } @Test public void test048() { test("test048_ok.flg"); } @Test public void test056() { test("test056_ok.flg"); } @Test public void test057() { test("test057_ok.flg"); } @Test public void test058() { test("test058_ok.flg"); } @Test public void test061() { test("test061_ok.flg"); } @Test public void test062() { test("test062_ok.flg"); } @Test public void test063() { test("test063_ok.flg"); } @Test public void test064() { test("test064_ok.flg"); } @Test public void test065() { test("test065_ok.flg"); } @Test public void test066() { test("test066_ok.flg"); } @Test public void test067() { test("test067_ok.flg"); } @Test public void test068() { test("test068_ok.flg"); } @Test public void test069() { test("test069_ok.flg"); } @Test public void test070() { test("test070_ok.flg"); } @Test public void test071() { test("test071_ok.flg"); } @Test public void test072() { test("test072_ok.flg"); } @Test public void test073() { test("test073_ok.flg"); } @Test public void test077() { test("test077_ok.flg"); } @Test public void test078() { test("test078_ok.flg"); } @Test public void test079() { test("test079_ok.flg"); } @Test public void test081() { test("test081_ok.flg"); } @Test public void test082() { test("test082_ok.flg"); } @Test public void test083() { test("test083_ok.flg"); } @Test public void test084() { test("test084_ok.flg"); } @Test public void test085() { test("test085_ok.flg"); } @Test public void test086() { test("test086_ok.flg"); } @Test public void test087() { test("test087_ok.flg"); } @Test public void test088() { test("test088_ok.flg"); } @Test public void test089() { test("test089_ok.flg"); } @Test public void test090() { test("test090_ok.flg"); } @Test public void test092() { test("test092_ok.flg"); } @Test public void test093() { test("test093_ok.flg"); } @Test public void test094() { test("test094_ok.flg"); } @Test public void test095() { test("test095_ok.flg"); } @Test public void test096() { test("test096_ok.flg"); } @Test public void test097() { test("test097_ok.flg"); } @Test public void test099() { test("test099_ok.flg"); } @Test public void test100() { test("test100_ok.flg"); } @Test public void test102() { test("test102_ok.flg"); } @Test public void test103() { test("test103_ok.flg"); } @Test public void test104() { test("test104_ok.flg"); } @Test public void test105() { test("test105_ok.flg"); } @Test public void test106() { test("test106_ok.flg"); } @Test public void test107() { test("test107_ok.flg"); } @Test public void test108() { test("test108_ok.flg"); } @Test public void test109() { test("test109_ok.flg"); } @Test public void test110() { test("test110_ok.flg"); } @Test public void test111() { test("test111_ok.flg"); } @Test public void test112() { test("test112_ok.flg"); } @Test public void test113() { test("test113_ok.flg"); } @Test public void test114() { test("test114_ok.flg"); } @Test public void test115() { test("test115_ok.flg"); } @Test public void test116() { test("test116_ok.flg"); } @Test public void test117() { test("test117_ok.flg"); } @Test public void test119() { test("test119_ok.flg"); } @Test public void test120() { test("test120_ok.flg"); } @Test public void test121() { test("test121_ok.flg"); } @Test public void test122() { test("test122_ok.flg"); } @Test public void test123() { test("test123_ok.flg"); } @Test public void test124() { test("test124_ok.flg"); } @Test public void test125() { test("test125_ok.flg"); } @Test public void test126() { test("test126_ok.flg"); } @Test public void test127() { test("test127_ok.flg"); } @Test public void test128() { test("test128_ok.flg"); } @Test public void test129() { test("test129_ok.flg"); } @Test public void test134() { test("test134_ok.flg"); } @Test public void test135() { test("test135_ok.flg"); } @Test public void test136() { test("test136_ok.flg"); } @Test public void test137() { test("test137_ok.flg"); } @Test public void test139() { test("test139_ok.flg"); } @Test public void test180() { test("test180_ok.flg"); } @Test public void test181() { test("test181_ok.flg"); } @Test public void test182() { test("test182_ok.flg"); } @Test public void test183() { test("test183_ok.flg"); } @Test public void test184() { test("test184_ok.flg"); } @Test public void test186() { test("test186_ok.flg"); } @Test public void test187() { test("test187_ok.flg"); } @Test public void test189() { test("test189_ok.flg"); } @Test public void test190() { test("test190_ok.flg"); } @Test public void test191() { test("test191_ok.flg", Arrays.asList("test191_input")); } @Test public void test192() { test("test192_ok.flg"); } @Test public void test193() { test("test193_ok.flg"); } @Test public void test238() { test("test238_ok.flg"); } @Test public void test240() { test("test240_ok.flg"); } @Test public void test241() { test("test241_ok.flg"); } @Test public void test242() { test("test242_ok.flg"); } @Test public void test243() { test("test243_ok.flg"); } @Test public void test244() { test("test244_ok.flg"); } @Test public void test245() { test("test245_ok.flg"); } @Test public void test254() { test("test254_ok.flg"); } @Test public void test256() { test("test256_ok.flg"); } @Test public void test258() { test("test258_ok.flg"); } @Test public void test259() { test("test259_ok.flg"); } @Test public void test260() { test("test260_ok.flg"); } @Test public void test261() { test("test261_ok.flg"); } @Test public void test262() { test("test262_ok.flg"); } @Test public void test263() { test("test263_ok.flg"); } @Test public void test264() { test("test264_ok.flg"); } @Test public void test265() { test("test265_ok.flg"); } @Test public void test267() { test("test267_ok.flg"); } @Test public void test273() { test("test273_ok.flg"); } @Test public void test274() { test("test274_ok.flg"); } @Test public void test275() { test("test275_ok.flg"); } @Test public void test276() { test("test276_ok.flg", Arrays.asList("test276_inputA", "test276_inputB")); } @Test public void test277() { test("test277_ok.flg"); } @Test public void test278() { test("test278_ok.flg"); } @Test public void test279() { test("test279_ok.flg"); } @Test public void test280() { test("test280_ok.flg"); } @Test public void test281() { test("test281_ok.flg"); } @Test public void test282() { test("test282_ok.flg"); } @Test public void test283() { test("test283_ok.flg"); } @Test public void test284() { test("test284_ok.flg"); } @Test public void test285() { test("test285_ok.flg"); } @Test public void test286() { test("test286_ok.flg"); } @Test public void test287() { test("test287_ok.flg"); } @Test public void test288() { test("test288_ok.flg"); } @Test public void test289() { test("test289_ok.flg"); } @Test public void test290() { test("test290_ok.flg"); } @Test public void test291() { test("test291_ok.flg"); } @Test public void test292() { test("test292_ok.flg"); } @Test public void test297() { test("test297_ok.flg"); } @Test public void test298() { test("test298_ok.flg"); } @Test public void test299() { test("test299_ok.flg"); } @Test public void test300() { test("test300_ok.flg"); } @Test public void test301() { test("test301_ok.flg"); } @Test public void test304() { test("test304_ok.flg"); } @Test public void test305() { test("test305_ok.flg"); } @Test public void test306() { test("test306_ok.flg"); } @Test public void test308() { test("test308_ok.flg"); } @Test public void test309() { test("test309_ok.flg"); } @Test public void test310() { test("test310_ok.flg"); } @Test public void test311() { test("test311_ok.flg"); } /* * No longer supporting these semantics * * @Test public void test316() { test("test316_ok.flg"); } * * @Test public void test317() { test("test317_ok.flg"); } * * @Test public void test318() { test("test318_ok.flg"); } * * @Test public void test319() { test("test319_ok.flg"); } */ @Test public void test320() { test("test320_ok.flg"); } @Test public void test321() { test("test321_ok.flg"); } @Test public void test323() { test("test323_ok.flg"); } @Test public void test324() { test("test324_ok.flg"); } @Test public void test325() { test("test325_ok.flg"); } @Test public void test326() { test("test326_ok.flg"); } @Test public void test328() { test("test328_ok.flg"); } @Test public void test329() { test("test329_ok.flg"); } @Test public void test330() { test("test330_ok.flg"); } @Test public void test331() { test("test331_ok.flg"); } @Test public void test332() { test("test332_ok.flg"); } @Test public void test334() { test("test334_ok.flg"); } @Test public void test336() { test("test336_ok.flg"); } @Test public void test337() { test("test337_ok.flg"); } @Test public void test338() { test("test338_ok.flg"); } @Test public void test339() { test("test339_ok.flg"); } @Test public void test340() { test("test340_ok.flg"); } @Test public void test345() { test("test345_ok.flg"); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/EagerSemiNaiveEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; public class EagerSemiNaiveEvaluationTest extends CommonEvaluationTest { public EagerSemiNaiveEvaluationTest() { super(new InterpretedSemiNaiveTester(true)); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/InterpretedSemiNaiveTester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import edu.harvard.seas.pl.formulog.symbols.RelationSymbol; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import edu.harvard.seas.pl.formulog.validating.InvalidProgramException; public class InterpretedSemiNaiveTester extends AbstractTester { private final boolean eagerEval; public InterpretedSemiNaiveTester(boolean eagerEval) { this.eagerEval = eagerEval; } @Override protected SemiNaiveEvaluation setup(WellTypedProgram prog) throws InvalidProgramException, EvaluationException { return SemiNaiveEvaluation.setup(prog, 2, eagerEval); } @Override protected boolean evaluate(SemiNaiveEvaluation eval) throws EvaluationException { eval.run(); EvaluationResult res = eval.getResult(); RelationSymbol sym; if (eval.hasQuery()) { sym = eval.getQuery().getSymbol(); } else { sym = (RelationSymbol) eval.getInputProgram().getSymbolManager().lookupSymbol("ok"); } return res.getAll(sym).iterator().hasNext(); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/SemiNaiveEvaluationTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; public class SemiNaiveEvaluationTest extends CommonEvaluationTest { public SemiNaiveEvaluationTest() { super(new InterpretedSemiNaiveTester(false)); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/eval/Tester.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.eval; import java.util.List; public interface Tester { void test(String file, List inputDirs); } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/magic/CommonMagicSetTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.eval.AbstractEvaluationTest; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.Tester; import org.junit.Test; public abstract class CommonMagicSetTest extends AbstractEvaluationTest { public CommonMagicSetTest(Tester tester) { super(tester); } @Test public void test140() { test("test140_ok.flg"); } @Test public void test141() { test("test141_ok.flg"); } @Test public void test142() { test("test142_ok.flg"); } @Test public void test143() { test("test143_ok.flg"); } @Test public void test144() { test("test144_ok.flg"); } @Test public void test145() { test("test145_ok.flg"); } @Test public void test146() { test("test146_ok.flg"); } @Test public void test147() { test("test147_ok.flg"); } @Test public void test148() { test("test148_ok.flg"); } @Test public void test149() { test("test149_ok.flg"); } @Test public void test150() { test("test150_ok.flg"); } @Test public void test151() { test("test151_ok.flg"); } @Test public void test152() { test("test152_ok.flg"); } @Test public void test153() { test("test153_ok.flg"); } @Test public void test154() { test("test154_ok.flg"); } @Test public void test155() { test("test155_ok.flg"); } @Test public void test156() { test("test156_ok.flg"); } @Test public void test157() { test("test157_ok.flg"); } @Test public void test158() { test("test158_ok.flg"); } @Test public void test159() { test("test159_ok.flg"); } @Test public void test160() { test("test160_ok.flg"); } @Test public void test161() { test("test161_ok.flg"); } @Test public void test162() { test("test162_ok.flg"); } @Test public void test163() { test("test163_ok.flg"); } @Test public void test164() { test("test164_ok.flg"); } @Test public void test165() { test("test165_ok.flg"); } @Test public void test166() { test("test166_ok.flg"); } @Test public void test167() { test("test167_ok.flg"); } @Test public void test168() { test("test168_ok.flg"); } @Test public void test169() { test("test169_ok.flg"); } @Test public void test170() { test("test170_ok.flg"); } @Test public void test171() { test("test171_ok.flg"); } @Test public void test172() { test("test172_ok.flg"); } @Test public void test173() { test("test173_ok.flg"); } @Test public void test174() { test("test174_ok.flg"); } @Test public void test175() { test("test175_ok.flg"); } @Test public void test176() { test("test176_ok.flg"); } @Test public void test177() { test("test177_ok.flg"); } @Test public void test178() { test("test178_ok.flg"); } @Test public void test179() { test("test179_ok.flg"); } @Test public void test249() { test("test249_ok.flg"); } @Test public void test250() { test("test250_ok.flg"); } @Test public void test252() { test("test252_ok.flg"); } @Test public void test253() { test("test253_ok.flg"); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/magic/EagerSemiNaiveMagicSetTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.eval.InterpretedSemiNaiveTester; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; public class EagerSemiNaiveMagicSetTest extends CommonMagicSetTest { public EagerSemiNaiveMagicSetTest() { super(new InterpretedSemiNaiveTester(true)); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/magic/SemiNaiveMagicSetTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.magic; import edu.harvard.seas.pl.formulog.eval.InterpretedSemiNaiveTester; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; public class SemiNaiveMagicSetTest extends CommonMagicSetTest { public SemiNaiveMagicSetTest() { super(new InterpretedSemiNaiveTester(false)); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/parsing/ParsingTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2024 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.parsing; import static org.junit.Assert.fail; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; import org.junit.Test; public class ParsingTest { void test(String file) { boolean isBad = file.matches("test\\d\\d\\d_bd.flg"); try { InputStream is = getClass().getClassLoader().getResourceAsStream(file); if (is == null) { throw new FileNotFoundException(file + " not found"); } (new Parser()).parse(new InputStreamReader(is)); } catch (ParseException e) { if (!isBad) { fail("Test failed for a good program: " + e.getMessage()); } return; } catch (Exception e) { fail("Unexpected exception: " + e.getMessage() + " " + e.getClass()); } if (isBad) { fail("Test succeeded for a bad program"); } } @Test public void test001() { test("test001_ok.flg"); } @Test public void test002() { test("test002_ok.flg"); } @Test public void test003() { test("test003_ok.flg"); } @Test public void test004() { test("test004_ok.flg"); } @Test public void test005() { test("test005_ok.flg"); } @Test public void test006() { test("test006_ok.flg"); } @Test public void test007() { test("test007_ok.flg"); } @Test public void test028() { test("test028_ok.flg"); } @Test public void test031() { test("test031_ok.flg"); } @Test public void test036() { test("test036_ok.flg"); } @Test public void test042() { test("test042_ok.flg"); } @Test public void test055() { test("test055_bd.flg"); } @Test public void test059() { test("test059_ok.flg"); } @Test public void test074() { test("test074_ok.flg"); } @Test public void test075() { test("test075_ok.flg"); } @Test public void test076() { test("test076_ok.flg"); } @Test public void test255() { test("test255_bd.flg"); } @Test public void test257() { test("test257_ok.flg"); } @Test public void test266() { test("test266_bd.flg"); } @Test public void test271() { test("test271_bd.flg"); } @Test public void test272() { test("test272_bd.flg"); } @Test public void test302() { test("test302_bd.flg"); } @Test public void test322() { test("test322_bd.flg"); } @Test public void test335() { test("test335_bd.flg"); } @Test public void test341() { test("test341_bd.flg"); } @Test public void test342() { test("test342_bd.flg"); } @Test public void test343() { test("test343_bd.flg"); } @Test public void test344() { test("test344_bd.flg"); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/types/TypeCheckingTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.types; import static org.junit.Assert.fail; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.parsing.Parser; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; import org.junit.Test; public class TypeCheckingTest { void test(String file) { boolean isBad = file.matches("test\\d\\d\\d_bd.flg"); try { InputStream is = getClass().getClassLoader().getResourceAsStream(file); if (is == null) { throw new FileNotFoundException(file + " not found"); } BasicProgram prog = (new Parser()).parse(new InputStreamReader(is)); (new TypeChecker(prog)).typeCheck(); } catch (TypeException e) { if (!isBad) { fail("Test failed for a good program: " + e.getMessage()); } return; } catch (Exception e) { fail("Unexpected exception: " + e.getMessage()); } if (isBad) { fail("Test succeeded for a bad program"); } } @Test public void test006() { test("test006_ok.flg"); } @Test public void test008() { test("test008_ok.flg"); } @Test public void test009() { test("test009_ok.flg"); } @Test public void test010() { test("test010_ok.flg"); } @Test public void test011() { test("test011_ok.flg"); } @Test public void test012() { test("test012_bd.flg"); } @Test public void test013() { test("test013_ok.flg"); } @Test public void test014() { test("test014_ok.flg"); } @Test public void test015() { test("test015_ok.flg"); } @Test public void test016() { test("test016_ok.flg"); } @Test public void test017() { test("test017_ok.flg"); } @Test public void test049() { test("test049_ok.flg"); } @Test public void test052() { test("test052_bd.flg"); } @Test public void test053() { test("test053_ok.flg"); } @Test public void test054() { test("test054_ok.flg"); } @Test public void test060() { test("test060_ok.flg"); } @Test public void test080() { test("test080_bd.flg"); } @Test public void test091() { test("test091_bd.flg"); } @Test public void test098() { test("test098_bd.flg"); } @Test public void test118() { test("test118_bd.flg"); } @Test public void test130() { test("test130_bd.flg"); } @Test public void test131() { test("test131_bd.flg"); } @Test public void test132() { test("test132_bd.flg"); } @Test public void test185() { test("test185_bd.flg"); } @Test public void test188() { test("test188_bd.flg"); } @Test public void test268() { test("test268_bd.flg"); } @Test public void test269() { test("test269_bd.flg"); } @Test public void test270() { test("test270_bd.flg"); } @Test public void test293() { test("test293_ok.flg"); } @Test public void test294() { test("test294_ok.flg"); } @Test public void test295() { test("test295_bd.flg"); } @Test public void test296() { test("test296_bd.flg"); } @Test public void test307() { test("test307_ok.flg"); } @Test public void test312() { test("test312_bd.flg"); } @Test public void test313() { test("test313_bd.flg"); } @Test public void test314() { test("test314_bd.flg"); } @Test public void test315() { test("test315_bd.flg"); } @Test public void test327() { test("test327_bd.flg"); } @Test public void test333() { test("test333_bd.flg"); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/validating/SemiNaiveValidatingTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.eval.SemiNaiveEvaluation; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; public class SemiNaiveValidatingTest extends ValidatingTest { @Override protected Evaluation setup(WellTypedProgram prog) throws InvalidProgramException, EvaluationException { return SemiNaiveEvaluation.setup(prog, 2, false); } } ================================================ FILE: src/test/java/edu/harvard/seas/pl/formulog/validating/ValidatingTest.java ================================================ /*- * #%L * Formulog * %% * Copyright (C) 2019-2023 President and Fellows of Harvard College * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package edu.harvard.seas.pl.formulog.validating; import static org.junit.Assert.fail; import edu.harvard.seas.pl.formulog.ast.BasicProgram; import edu.harvard.seas.pl.formulog.eval.Evaluation; import edu.harvard.seas.pl.formulog.eval.EvaluationException; import edu.harvard.seas.pl.formulog.parsing.Parser; import edu.harvard.seas.pl.formulog.types.TypeChecker; import edu.harvard.seas.pl.formulog.types.WellTypedProgram; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; import org.junit.Test; public abstract class ValidatingTest { void test(String file) { boolean isBad = file.matches("test\\d\\d\\d_bd.flg"); try { InputStream is = getClass().getClassLoader().getResourceAsStream(file); if (is == null) { throw new FileNotFoundException(file + " not found"); } BasicProgram prog = (new Parser()).parse(new InputStreamReader(is)); setup(new TypeChecker(prog).typeCheck()); if (isBad) { fail("Test succeeded for a bad program"); } } catch (InvalidProgramException e) { if (!isBad) { fail("Test failed for a good program"); } } catch (Exception e) { fail("Unexpected exception: " + e.getMessage()); } } protected abstract Evaluation setup(WellTypedProgram prog) throws InvalidProgramException, EvaluationException; @Test public void test025() { test("test025_bd.flg"); } @Test public void test026() { test("test026_bd.flg"); } @Test public void test050() { test("test050_bd.flg"); } @Test public void test051() { test("test051_bd.flg"); } @Test public void test133() { test("test133_ok.flg"); } @Test public void test217() { test("test217_bd.flg"); } @Test public void test218() { test("test218_bd.flg"); } @Test public void test219() { test("test219_ok.flg"); } @Test public void test248() { test("test248_bd.flg"); } @Test public void test251() { test("test251_bd.flg"); } } ================================================ FILE: src/test/resources/test001_ok.flg ================================================ (* okay *) @edb rel p0. @edb rel p1(i32). @edb rel p2(i32, i32). @edb rel p3(i32, i32, i32). @edb rel p4(i32, i32, i32, i32). ================================================ FILE: src/test/resources/test002_ok.flg ================================================ (* okay *) rel p0. rel p1(i32). rel p2(i32, i32). rel p3(i32, i32, i32). rel p4(i32, i32, i32, i32). ================================================ FILE: src/test/resources/test003_ok.flg ================================================ (* okay *) fun f0 : i32 = 42. fun f1(_X0:i32) : i32 = 42. fun f2(_X0:i32, _X1:i32) : i32 = 42. fun f3(_X0:i32, _X1:i32, _X2:i32) : i32 = 42. fun f4(_X0:i32, _X1:i32, _X2:i32, _X3:i32) : i32 = 42. ================================================ FILE: src/test/resources/test004_ok.flg ================================================ type i32_alias = i32. type i32x2 = (i32 * i32). type i32x3 = (i32 * i32 * i32). type i32x4 = (i32 * i32 * i32 * i32). type i32_alias_pair = (i32_alias * i32_alias). type i32_list = i32_list_nil | i32_list_cons(i32, i32_list). type 'a my_list = | my_nil | my_cons('a, 'a my_list). type string_alias = string. type i32_list_2 = i32 my_list. type ('k, 'v) assoc_list = ('k * 'v) my_list. type 'a alias = 'a. ================================================ FILE: src/test/resources/test005_ok.flg ================================================ type 'a my_list = | my_nil | my_cons('a, 'a my_list). type ('k, 'v) assoc_list = ('k * 'v) my_list. type 'a my_option = | my_none | my_some('a). fun lookup(Key:'k, Map:('k, 'v) assoc_list) : 'v my_option = match Map with | my_nil => my_none (* this doesn't actually do what you think, since key is rebound in the pattern *) | my_cons((_Key, Val), _) => my_some(Val) | my_cons(_, Rest) => lookup(Key, Rest) end. ================================================ FILE: src/test/resources/test006_ok.flg ================================================ type node = i32. @edb rel edge(node, node). @edb rel vertex(node). rel notWellFormed. rel reach(node, node). rel inCycle(node). rel notInCycle(node). rel hasOtherNeighbor(node). notWellFormed :- edge(X, _), !vertex(X). notWellFormed :- edge(_, X), !vertex(X). reach(X, Y) :- edge(X, Y). reach(X, Y) :- edge(X, Z), reach(Z, Y). inCycle(X) :- reach(X, Y), X = Y. notInCycle(X) :- vertex(X), !inCycle(X). hasOtherNeighbor(X) :- edge(X, Y), X != Y. ================================================ FILE: src/test/resources/test007_ok.flg ================================================ @edb rel p(string). p("hello"). p(""). ================================================ FILE: src/test/resources/test008_ok.flg ================================================ @edb rel p0. @edb rel p_i32(i32). @edb rel p_i32_2x(i32, i32). @edb rel p_string(string). @edb rel p_string_2x(string, string). p0. p_i32(0). p_i32(-42). p_i32(42). p_i32_2x(0, 0). p_i32_2x(0, -42). p_i32_2x(0, 42). p_i32_2x(-42, 0). p_i32_2x(-42, -42). p_i32_2x(-42, 42). p_i32_2x(42, 0). p_i32_2x(42, -42). p_i32_2x(42, 42). p_string(""). p_string("hello"). p_string("goodbye"). p_string_2x("", ""). p_string_2x("", "hello"). p_string_2x("", "goodbye"). p_string_2x("hello", ""). p_string_2x("hello", "hello"). p_string_2x("hello", "goodbye"). p_string_2x("goodbye", ""). p_string_2x("goodbye", "hello"). p_string_2x("goodbye", "goodbye"). ================================================ FILE: src/test/resources/test009_ok.flg ================================================ @edb rel p0. type 'a alias = 'a. @edb rel p_i32(i32 alias). @edb rel p_i32_2x(i32 alias, i32 alias). @edb rel p_string(string alias). @edb rel p_string_2x(string alias, string alias). p0. p_i32(0). p_i32(-42). p_i32(42). p_i32_2x(0, 0). p_i32_2x(0, -42). p_i32_2x(0, 42). p_i32_2x(-42, 0). p_i32_2x(-42, -42). p_i32_2x(-42, 42). p_i32_2x(42, 0). p_i32_2x(42, -42). p_i32_2x(42, 42). p_string(""). p_string("hello"). p_string("goodbye"). p_string_2x("", ""). p_string_2x("", "hello"). p_string_2x("", "goodbye"). p_string_2x("hello", ""). p_string_2x("hello", "hello"). p_string_2x("hello", "goodbye"). p_string_2x("goodbye", ""). p_string_2x("goodbye", "hello"). p_string_2x("goodbye", "goodbye"). ================================================ FILE: src/test/resources/test010_ok.flg ================================================ @edb rel p0. type 'a alias = 'a. type i32_alias = i32 alias. type string_alias = string alias. @edb rel p_i32(i32_alias). @edb rel p_i32_2x(i32_alias, i32_alias). @edb rel p_string(string_alias). @edb rel p_string_2x(string_alias, string_alias). p0. p_i32(0). p_i32(-42). p_i32(42). p_i32_2x(0, 0). p_i32_2x(0, -42). p_i32_2x(0, 42). p_i32_2x(-42, 0). p_i32_2x(-42, -42). p_i32_2x(-42, 42). p_i32_2x(42, 0). p_i32_2x(42, -42). p_i32_2x(42, 42). p_string(""). p_string("hello"). p_string("goodbye"). p_string_2x("", ""). p_string_2x("", "hello"). p_string_2x("", "goodbye"). p_string_2x("hello", ""). p_string_2x("hello", "hello"). p_string_2x("hello", "goodbye"). p_string_2x("goodbye", ""). p_string_2x("goodbye", "hello"). p_string_2x("goodbye", "goodbye"). ================================================ FILE: src/test/resources/test011_ok.flg ================================================ type ('a, 'b) pair = ('a * 'b). @edb rel p_i32_string((i32, string) pair). type i32_pair = (i32, i32) pair. @edb rel pair_of_i32_pair((i32_pair, i32_pair) pair). p_i32_string((0, "")). p_i32_string((42, "hello")). p_i32_string((-42, "goodbye")). pair_of_i32_pair(((0, 0), (-42, 42))). pair_of_i32_pair(((-42, 0), (0, 42))). ================================================ FILE: src/test/resources/test012_bd.flg ================================================ @edb rel str(string). @edb rel num(i32). rel foo(i32, string). foo(X, Y) :- str(X), str(Y). ================================================ FILE: src/test/resources/test013_ok.flg ================================================ @edb rel rel1(i32, string). @edb rel rel2(i32, string). rel join(i32, (string * string)). join(Id, (Str1, Str2)) :- rel1(Id, Str1), rel2(Id, Str2). (* an equivalent (more verbose) formulation: *) join(Id1, Pair) :- rel1(Id1, Str1), rel2(Id2, Str2), Id1 = Id2, Pair = (Str1, Str2). ================================================ FILE: src/test/resources/test014_ok.flg ================================================ fun f(X:i32) : i32 = X. fun g(X:'a) : 'a = X. fun h(_X:string) : i32 = 42. fun h_inv(_X:i32) : string = "hello". @edb rel q(i32). rel p(i32). p(Y) :- Y = f(X), q(X). p(Y) :- Y = g(X), q(X). p(Y) :- Y = f(h(g(h_inv(X)))), q(X). ================================================ FILE: src/test/resources/test015_ok.flg ================================================ type ('a, 'b) union = | inj_l('a) | inj_r('b). @edb rel q((i32, string) union). rel p(i32). rel r(string). p(X) :- q(inj_l(X)). r(X) :- q(inj_r(X)). ================================================ FILE: src/test/resources/test016_ok.flg ================================================ type ('a, 'b) union = | inj_l('a) | inj_r('b). type color = (i32, (i32*i32*i32)) union. type id = (string, i32) union. fun str2i32(_S:string) : i32 = 42. @edb rel colors(color). @edb rel ids(id). rel gray(i32, i32). rel rgb(i32, i32, i32, i32). gray(Id, Color) :- ids(inj_r(Id)), colors(inj_l(Color)). gray(Id, Color) :- ids(inj_l(Id_str)), colors(inj_l(Color)), Id = str2i32(Id_str). rgb(Id, R, G, B) :- ids(inj_r(Id)), colors(inj_r((R, G, B))). rgb(Id, R, G, B) :- ids(inj_l(Id_str)), colors(inj_r((R, G, B))), Id = str2i32(Id_str). ================================================ FILE: src/test/resources/test017_ok.flg ================================================ type nat = | o | s(nat). fun lookup(Idx:nat, List:'a list) : 'a option = match (Idx, List) with | (_, []) => none | (o, X :: _) => some(X) | (s(Idx), _ :: List) => lookup(Idx, List) end. fun loop : 'a = loop. type false_term = . fun false_exists : false_term = loop. fun id(X:'a) : 'a = X. ================================================ FILE: src/test/resources/test018_ok.flg ================================================ @edb rel r. rel q. rel p. rel ok. p :- q. q :- r. r. ok :- p, q, r. ================================================ FILE: src/test/resources/test019_ok.flg ================================================ @edb rel r(i32). rel q(i32). rel p(i32). rel ok. p(X) :- q(X). q(X) :- r(X). r(-42). r(0). r(42). ok :- r(-42), r(0), r(42), q(-42), q(0), q(42), p(-42), p(0), p(42). ================================================ FILE: src/test/resources/test020_ok.flg ================================================ type node = i32. @edb rel edge(node, node). @edb rel vertex(node). rel notWellFormed. rel reach(node, node). rel inCycle(node). rel notInCycle(node). rel hasOtherNeighbor(node). rel ok. notWellFormed :- edge(X, _), !vertex(X). notWellFormed :- edge(_, X), !vertex(X). reach(X, Y) :- edge(X, Y). reach(X, Y) :- edge(X, Z), reach(Z, Y). inCycle(X) :- reach(X, Y), X = Y. notInCycle(X) :- vertex(X), !inCycle(X). hasOtherNeighbor(X) :- edge(X, Y), X != Y. vertex(0). vertex(1). vertex(2). vertex(3). vertex(4). edge(0, 1). edge(1, 2). edge(2, 3). edge(3, 4). edge(4, 4). ok :- vertex(0), vertex(1), vertex(2), vertex(3), vertex(4), edge(0, 1), edge(1, 2), edge(2, 3), edge(3, 4), edge(4, 4), !notWellFormed, reach(0, 1), reach(0, 2), reach(0, 3), reach(0, 4), reach(1, 2), reach(1, 3), reach(1, 4), reach(2, 3), reach(2, 4), reach(3, 4), reach(4, 4), inCycle(4), notInCycle(0), notInCycle(1), notInCycle(2), notInCycle(3), hasOtherNeighbor(0), hasOtherNeighbor(1), hasOtherNeighbor(2), hasOtherNeighbor(3), !hasOtherNeighbor(4). ================================================ FILE: src/test/resources/test021_ok.flg ================================================ rel ok. @edb rel input_list(i32 list). rel palindrome(i32 list). rel fail. fun list_rev_helper(Xs:'a list, Acc:'a list) : 'a list = match Xs with | [] => Acc | X :: Rest => list_rev_helper(Rest, X :: Acc) end. fun list_rev(Xs:'a list) : 'a list = list_rev_helper(Xs, []). palindrome(Xs) :- input_list(Xs), list_rev(Xs) = Xs. fail :- input_list(Xs), palindrome(Xs), list_rev(Xs) != Xs. input_list([]). input_list([1]). input_list([1, 42, 1]). input_list([1, 42, 42, 1]). input_list([1, 2, 3, 4]). ok :- palindrome([]), palindrome([1]), palindrome([1, 42, 1]), palindrome([1, 42, 42, 1]), !palindrome([1, 2, 3, 4]), !fail. ================================================ FILE: src/test/resources/test022_ok.flg ================================================ rel base_ok. rel neg_ok. rel and_ok. rel or_ok. rel ok. base_ok :- is_sat(`true`), !is_sat(`false`). neg_ok :- !is_sat(`~true`), is_sat(`~false`), is_sat(`~~true`), !is_sat(`~~false`). and_ok :- is_sat(`true /\ true`), !is_sat(`true /\ false`), !is_sat(`false /\ true`), !is_sat(`false /\ false`). or_ok :- is_sat(`true \/ true`), is_sat(`true \/ false`), is_sat(`false \/ true`), !is_sat(`false \/ false`). ok :- base_ok, neg_ok, and_ok, or_ok. ================================================ FILE: src/test/resources/test023_ok.flg ================================================ rel ok. ok :- is_sat(`42 #= 42`), X = `42`, Y = X, is_sat(`X #= Y`), Z = `43`, !is_sat(`Y #= Z`), is_sat(`~(Y #= Z)`). ================================================ FILE: src/test/resources/test024_ok.flg ================================================ rel ok. ok :- X = 42, 0 = X + 2 + -44, X = 20 + 22, X = 2 + 20 * 2, X = 20 * 2 + 2, Y = 20 * (2 + 2), Y = 80, Y / (2 + 2) = 20 % 21, X = 41 - -1. ================================================ FILE: src/test/resources/test025_bd.flg ================================================ @edb rel p. rel fail(i32). fail(_X) :- p. ================================================ FILE: src/test/resources/test026_bd.flg ================================================ rel ok. fun f(X:'a) : 'a = X. type blah = | a(i32, i32, i32, i32, i32) | b(i32, i32, i32). ok :- a(f(X3), f(X2), f(X1), f(X0), 0) = a(_X4, X3, X2, X1, X0). ================================================ FILE: src/test/resources/test027_ok.flg ================================================ type node = i32. @edb rel edge(node, node). @edb rel vertex(node). rel notWellFormed. rel reach(node, node). rel inCycle(node). rel notInCycle(node). rel hasOtherNeighbor(node). rel ok. notWellFormed :- edge(X, _), !vertex(X). notWellFormed :- edge(_, X), !vertex(X). reach(X, Y) :- edge(X, Y). reach(X, Y) :- reach(X, Z), reach(Z, Y). inCycle(X) :- reach(X, Y), X = Y. notInCycle(X) :- vertex(X), !inCycle(X). hasOtherNeighbor(X) :- edge(X, Y), X != Y. vertex(0). vertex(1). vertex(2). vertex(3). vertex(4). edge(0, 1). edge(1, 2). edge(2, 3). edge(3, 4). edge(4, 4). ok :- vertex(0), vertex(1), vertex(2), vertex(3), vertex(4), edge(0, 1), edge(1, 2), edge(2, 3), edge(3, 4), edge(4, 4), !notWellFormed, reach(0, 1), reach(0, 2), reach(0, 3), reach(0, 4), reach(1, 2), reach(1, 3), reach(1, 4), reach(2, 3), reach(2, 4), reach(3, 4), reach(4, 4), inCycle(4), notInCycle(0), notInCycle(1), notInCycle(2), notInCycle(3), hasOtherNeighbor(0), hasOtherNeighbor(1), hasOtherNeighbor(2), hasOtherNeighbor(3), !hasOtherNeighbor(4). ================================================ FILE: src/test/resources/test028_ok.flg ================================================ @edb rel foo(i64). foo(42l). foo(42L). foo(-42l). foo(-42L). foo(+42l). foo(+42L). ================================================ FILE: src/test/resources/test029_ok.flg ================================================ @edb rel foo(i64). foo(0L). foo(-1L). rel add(i64, i64, i64). add(X, Y, Z) :- foo(X), foo(Y), Z = i64_add(X, Y). rel ok. ok :- add(0L, 0L, 0L), add(0L, -1L, -1L), add(-0L, -1L, -1L), add(-1L, -1L, -2L). ================================================ FILE: src/test/resources/test030_ok.flg ================================================ rel something_true. rel something_false1. rel something_false2. rel ok. something_true :- X = #{"x"}[bv[64]], Y = `bv_big_const[64](42L)`, F = `X #= Y`, is_sat(F), is_sat(`~F`). something_false1 :- X = #x[bv[64]], Y = `bv_big_const[64](42L)`, F = `X #= Y`, is_sat(`F /\ ~F`). something_false2 :- X = #{"x"}[bv[64]], Y = #x[bv[64]], is_sat(`~(X #= Y)`). ok :- something_true, !something_false1, !something_false2. ================================================ FILE: src/test/resources/test031_ok.flg ================================================ @edb rel foo(fp32). @edb rel bar(fp64). foo(42.0f). foo(42.0e1f). foo(42.0e-1f). foo(42.0e+1f). foo(-42.42F). foo(-0.42F). foo(-42F). foo(-42.0e1f). foo(-42.0e-1f). foo(-42.0e+1f). foo(+42.42F). foo(+0.42F). foo(+42F). foo(+42.0e1f). foo(+42.0e-1f). foo(+42.0e+1f). bar(42.0d). bar(42.0e1d). bar(42.0e-1d). bar(42.0e+1d). bar(-42.42D). bar(-0.42D). bar(-42D). bar(-42.0e1d). bar(-42.0e-1d). bar(-42.0e+1d). bar(+42.42D). bar(+0.42D). bar(+42D). bar(+42.0e1d). bar(+42.0e-1d). bar(+42.0e+1d). ================================================ FILE: src/test/resources/test032_ok.flg ================================================ @edb rel foo(fp32). foo(0F). foo(-1F). rel add(fp32, fp32, fp32). add(X, Y, Z) :- foo(X), foo(Y), Z = fp32_add(X, Y). rel ok. ok :- add(0F, 0F, 0F), add(0F, -1F, -1F), add(-1F, 0F, -1F), add(-1F, -1F, -2F). ================================================ FILE: src/test/resources/test033_ok.flg ================================================ @edb rel foo(fp64). foo(0D). foo(-1D). rel add(fp64, fp64, fp64). add(X, Y, Z) :- foo(X), foo(Y), Z = fp64_add(X, Y). rel ok. ok :- add(0D, 0D, 0D), add(0D, -1D, -1D), add(-1D, 0D, -1D), add(-1D, -1D, -2D). ================================================ FILE: src/test/resources/test034_ok.flg ================================================ rel something_true. rel something_false. rel ok. something_true :- X = #x[fp[64]], Y = `fp_big_const[64](42D)`, is_sat(`X #= Y`), is_sat(`~(X #= Y)`). something_false :- X = #{"x"}[fp[64]], Y = `fp_big_const[64](42D)`, F = `X #= Y`, is_sat(`F /\ ~F`). ok :- something_true, !something_false. ================================================ FILE: src/test/resources/test035_ok.flg ================================================ rel something_true. rel something_false. rel ok. something_true :- X = #x[fp[32]], Y = `fp_const[32](42F)`, F = `X #= Y`, is_sat(F), is_sat(`~F`). something_false :- X = #{"x"}[fp[32]], Y = `fp_const[32](42F)`, F = `X #= Y`, is_sat(`F /\ ~F`). ok :- something_true, !something_false. ================================================ FILE: src/test/resources/test036_ok.flg ================================================ @edb rel foo(fp32). @edb rel bar(fp64). foo(fp32_nan). foo(fp32_neg_infinity). foo(fp32_pos_infinity). bar(fp64_nan). bar(fp64_neg_infinity). bar(fp64_pos_infinity). ================================================ FILE: src/test/resources/test037_ok.flg ================================================ rel something_true. rel something_false1. rel something_false2. rel ok. something_true :- fp32_nan = fp32_nan, fp64_nan = fp64_nan. something_false1 :- fp32_eq(fp32_nan, fp32_nan) = true. something_false2 :- fp64_eq(fp64_nan, fp64_nan) = true. ok :- something_true, !something_false1, !something_false2. ================================================ FILE: src/test/resources/test038_ok.flg ================================================ rel ok. ok :- is_sat(`0 #= bv_to_bv_unsigned[64,32](0L)`), is_sat(`0 #= fp_to_sbv[32,32](0F)`), is_sat(`0 #= fp_to_sbv[64,32](0D)`), is_sat(`0 #= fp_to_ubv[32,32](0F)`), is_sat(`0 #= fp_to_ubv[64,32](0D)`), is_sat(`0L #= bv_to_bv_unsigned[32,64](0)`), is_sat(`0L #= fp_to_sbv[32,64](0F)`), is_sat(`0L #= fp_to_sbv[64,64](0D)`), is_sat(`0L #= fp_to_ubv[32,64](0F)`), is_sat(`0L #= fp_to_ubv[64,64](0D)`), is_sat(`0F #= bv_to_fp[32,32](0)`), is_sat(`0F #= bv_to_fp[64,32](0L)`), is_sat(`0F #= fp_to_fp[64,32](0D)`), is_sat(`0D #= bv_to_fp[32,64](0)`), is_sat(`0D #= bv_to_fp[64,64](0L)`), is_sat(`0D #= fp_to_fp[32,64](0F)`), true = true. ================================================ FILE: src/test/resources/test039_ok.flg ================================================ rel ok. ok :- X = #x[fp[32]], !is_sat(`fp_lt(fp32_pos_infinity, X)`), !is_sat(`fp_gt(fp32_neg_infinity, X)`), !is_sat(`fp_eq(fp32_nan, fp32_nan)`), is_valid(`fp_is_nan(fp32_nan)`), Y = #x[fp[64]], !is_sat(`fp_lt(fp64_pos_infinity, Y)`), !is_sat(`fp_gt(fp64_neg_infinity, Y)`), !is_sat(`fp_eq(fp64_nan, fp64_nan)`), is_valid(`fp_is_nan(fp64_nan)`). ================================================ FILE: src/test/resources/test040_ok.flg ================================================ rel ok. ok :- is_sat(`#if true then true else false`), !is_sat(`#if true then false else true`), is_sat(`(#if true then 1 else 0) #= 1`), !is_sat(`(#if false then 1 else 0) #= 1`), is_sat(`(#if true then 1L else 0L) #= 1L`), !is_sat(`(#if false then 1L else 0L) #= 1L`), is_sat(`(#if true then 1F else 0F) #= 1F`), !is_sat(`(#if false then 1F else 0F) #= 1F`), is_sat(`(#if true then 1D else 0D) #= 1D`), !is_sat(`(#if false then 1D else 0D) #= 1D`), true = true. ================================================ FILE: src/test/resources/test041_ok.flg ================================================ (* test how fp conversion works. *) rel ok. ok :- is_valid(`fp_is_nan(fp_to_fp[64,32](fp64_nan))`), is_valid(`fp32_pos_infinity #= fp_to_fp[64,32](fp64_pos_infinity)`), is_valid(`fp32_neg_infinity #= fp_to_fp[64,32](fp64_neg_infinity)`), is_valid(`fp_is_nan(fp_to_fp[32,64](fp32_nan))`), is_valid(`fp64_pos_infinity #= fp_to_fp[32,64](fp32_pos_infinity)`), is_valid(`fp64_neg_infinity #= fp_to_fp[32,64](fp32_neg_infinity)`). ================================================ FILE: src/test/resources/test042_ok.flg ================================================ type prim = | prim_byte(bv[32] smt) | prim_short(bv[32] smt). type java_prim_type = | java_prim_type_int. fun handle_concrete_conversion(V:prim, Type:java_prim_type) : prim = match Type with | java_prim_type_int => let Vv = match V with | prim_byte(_X) => `42` | prim_byte(X) | prim_short(X) => X end in prim_short(Vv) end. ================================================ FILE: src/test/resources/test043_ok.flg ================================================ @edb rel foo(i32). rel ok. foo(42). fun bar(X:i32) : bool = foo(X). ok :- bar(42) = true, bar(0) = false. ================================================ FILE: src/test/resources/test044_ok.flg ================================================ @edb rel foo(i32, i32). rel ok. fun bar(X:i32) : bool = foo(X, 13). fun baz(X:i32) : bool = foo(17, X). foo(42, 13). foo(17, 42). ok :- bar(42) = true, bar(0) = false, baz(42) = true, baz(0) = false. ================================================ FILE: src/test/resources/test045_ok.flg ================================================ @edb rel foo(i32, i32) rel ok foo(42, 13). foo(17, 42). ok :- X = 42, Y = 17, Z = 13, foo(X, Y) = false, foo(X, Z) = true, foo(Y, X) = true, foo(Y, Z) = false, foo(Z, X) = false, foo(Z, Y) = false. ================================================ FILE: src/test/resources/test046_ok.flg ================================================ @edb rel foo(i32, bool). @edb rel bar(i32). rel ok. foo(42, true). foo(0, false). bar(42). ok :- foo(42, bar(42)), foo(0, bar(0)). ================================================ FILE: src/test/resources/test047_ok.flg ================================================ @edb rel foo(i32, bool). @edb rel bar(i32). rel ok. foo(42, true). foo(0, false). bar(42). ok :- foo(X, bar(X)), foo(Y, bar(Y)), X != Y. ================================================ FILE: src/test/resources/test048_ok.flg ================================================ @edb rel foo(i32, bool). rel ok. foo(42, true). foo(0, false). fun bar(X:i32) : bool = X = 42. ok :- foo(X, bar(X)), foo(Y, bar(Y)), X != Y. ================================================ FILE: src/test/resources/test049_ok.flg ================================================ fun f(Xs:'a list) : 'a list = Xs. fun g(Xs:string list, Ys:i32 list) : (string list * i32 list) = (f(Xs), f(Ys)). ================================================ FILE: src/test/resources/test050_bd.flg ================================================ rel foo(i32). fun bar(X:i32) : i32 = if foo(X) then -X else X. foo(X) :- bar(42) = X. ================================================ FILE: src/test/resources/test051_bd.flg ================================================ rel foo(i32). fun bar(X:i32) : i32 = if baz(X) then -X else X and baz(X:i32) : bool = foo(X). foo(X) :- bar(42) = X. ================================================ FILE: src/test/resources/test052_bd.flg ================================================ @edb rel foo(bv[32] smt). foo(`42L`). ================================================ FILE: src/test/resources/test053_ok.flg ================================================ @edb rel foo(bv[32] smt). foo(`42`). foo(`bv_add(42, 77)`). foo(`bv_to_bv_unsigned[64,32](128L)`). ================================================ FILE: src/test/resources/test054_ok.flg ================================================ uninterpreted fun foo(bv[32] smt, fp[32] smt) : bool smt. ================================================ FILE: src/test/resources/test055_bd.flg ================================================ type bool_expr = foo(i32, fp_expr[24,8]). ================================================ FILE: src/test/resources/test056_ok.flg ================================================ rel ok. rel something_true. rel something_false. uninterpreted fun foo(bv[32] smt) : bv[32] smt. fun exp1 : bool smt = `foo(42) #= 0`. fun exp2 : bool smt = `foo(42) #= 1`. something_true :- is_sat(exp1), is_sat(exp2). something_false :- is_sat(`exp1 /\ exp2`). ok :- something_true, !something_false. ================================================ FILE: src/test/resources/test057_ok.flg ================================================ rel ok. rel something_true. rel something_false. uninterpreted fun foo(bv[32] smt) : fp[32] smt. fun exp1 : bool smt = `foo(42) #= 0F`. fun exp2 : bool smt = `foo(42) #= 1F`. something_true :- is_sat(exp1), is_sat(exp2). something_false :- is_sat(`exp1 /\ exp2`). ok :- something_true, !something_false. ================================================ FILE: src/test/resources/test058_ok.flg ================================================ rel ok. rel something_true. rel something_false. uninterpreted fun foo(bv[32] smt) : bool smt. fun exp1 : bool smt = `foo(42) #= true`. fun exp2 : bool smt = `foo(42) #= false`. something_true :- is_sat(exp1), is_sat(exp2). something_false :- is_sat(`exp1 /\ exp2`). ok :- something_true, !something_false. ================================================ FILE: src/test/resources/test059_ok.flg ================================================ type foo = | bar | baz1(i32) | baz2(string) | baz3(i32) | baz4(string). ================================================ FILE: src/test/resources/test060_ok.flg ================================================ fun bar : 'a list = []. rel foo. foo :- _X = 1 :: 2 :: bar. ================================================ FILE: src/test/resources/test061_ok.flg ================================================ rel ok ok. (* rel ok1. ok1 :- X = #x[bv[32]], Y = #y[bv[32]], Z = #z[bv[32]], E1 = `42 #= bv_add(X, Y)`, E2 = `42 #= bv_add(Z, Y)`, substitute(X, `Z`, E1) = E2. rel ok2. ok2 :- X = "x", Y = "y", Z = "z", E1 = `42 #= bv_add(#{X}[bv[32]], #{Y}[bv[32]])`, E2 = `42 #= bv_add(#{Z}[bv[32]], #{Y}[bv[32]])`, substitute(#{X}[bv[32]], `#{Z}[bv[32]]`, E1) = E2. rel ok3. ok3 :- X = #x[bv[32]], Y = #y[bv[32]], E = `#let X = Y in #let X = 42 in bv_add(X, X) #= 84`, is_valid(E). rel ok4. ok4 :- X = #x[bv[32]], E = `#let X = 42 in bv_add(X, X) #= 84`, is_valid(substitute(X, `0`, E)). (* Make sure that we are avoiding variable capture correctly. *) rel ok5. ok5 :- X = #x[bv[32]], Y = #y[bv[32]], E = `#let X = 0 in bv_add(Y, Y) #= 42`, is_sat(substitute(Y, `X`, E)). (* Make sure that substitute is deterministic. *) rel ok6. ok6 :- X = #x[bv[32]], Y = #y[bv[32]], E = `#let X = 42 in bv_add(X, Y)`, substitute(Y, `21`, E) = substitute(Y, `21`, E). rel ok. ok :- ok1, ok2, ok3, ok4, ok5, ok6. *) ================================================ FILE: src/test/resources/test062_ok.flg ================================================ @edb rel bar(i32). bar(2). rel foo(i32, i32, i32). foo(1, 2, 1 + 2). foo(X, Y, X + Y) :- bar(X), bar(Y). rel ok. ok :- foo(1,2,3), foo(2,2,4). ================================================ FILE: src/test/resources/test063_ok.flg ================================================ rel ok. ok :- A = #a[bool], B = #b[bool], is_valid(`A /\ B ==> A \/ B`), !is_valid(`A \/ B ==> A /\ B`). ================================================ FILE: src/test/resources/test064_ok.flg ================================================ type foo = | foo1 | foo2. rel ok. ok :- _ = `foo1 #= foo2`. ================================================ FILE: src/test/resources/test065_ok.flg ================================================ type foo = | foo1 | foo2. rel ok. ok :- X = `foo1 #= foo2`, !is_sat(X). ================================================ FILE: src/test/resources/test066_ok.flg ================================================ type foo = | foo1 | foo2. rel ok. ok :- X = `#x[foo] #= foo2`, is_sat(X). ================================================ FILE: src/test/resources/test067_ok.flg ================================================ type foo = | foo1 | foo2. rel ok. ok :- X = #x[foo], Y = `X #= foo1`, is_sat(Y), Z = `X #= foo2`, is_sat(Z). ================================================ FILE: src/test/resources/test068_ok.flg ================================================ type foo = | foo1 | foo2. rel ok. ok :- X = #x[foo], Y = `X #= foo1`, is_sat(Y), Z = `X #= foo2`, is_sat(Z), !is_sat(`Y /\ Z`). ================================================ FILE: src/test/resources/test069_ok.flg ================================================ type 'a my_list = | my_nil | my_cons('a, 'a my_list) rel ok ok :- X = #x[i32 my_list], is_sat(`X #= my_nil`). ================================================ FILE: src/test/resources/test070_ok.flg ================================================ type 'a my_list = | my_nil | my_cons('a, 'a my_list). rel ok. ok :- X = #x[i32 my_list], is_sat(`X #= my_cons(42, my_nil)`). ================================================ FILE: src/test/resources/test071_ok.flg ================================================ type 'a my_list = | my_nil | my_cons('a, 'a my_list). rel ok. ok :- X = #x[i32 my_list], Y = `X #= my_cons(42, my_nil)`, is_sat(Y), Z = `X #= my_nil`, is_sat(Z), !is_sat(`Y /\ Z`). ================================================ FILE: src/test/resources/test072_ok.flg ================================================ rel ok. ok :- X = #x[i32 list], Y = `X #= [1, 2, 3]`, is_sat(Y), Z = `X #= []`, is_sat(Z), !is_sat(`Y /\ Z`). ================================================ FILE: src/test/resources/test073_ok.flg ================================================ rel ok. ok :- X = #x[i32 option], Y = `X #= some(42)`, is_sat(Y), Z = `X #= none`, is_sat(Z), !is_sat(`Y /\ Z`). ================================================ FILE: src/test/resources/test074_ok.flg ================================================ type foo = | foo1 and bar = | bar1. ================================================ FILE: src/test/resources/test075_ok.flg ================================================ type 'a foo = | foo1(i32) | foo2('a) and ('a, 'b) bar = | bar1('a) | bar2('b). ================================================ FILE: src/test/resources/test076_ok.flg ================================================ type 'a foo = | foo1(('a, 'a) bar) | foo2('a foo) and ('a, 'b) bar = | bar1('a foo) | bar2('b foo) | bar3(('a foo, 'b foo) bar). ================================================ FILE: src/test/resources/test077_ok.flg ================================================ type foo = | foo1(i32) | foo2(bar) and bar = | bar1(i32) | bar2(foo). rel ok. ok :- X = #x[foo], Y = #y[bar], Eq1 = `X #= foo1(42)`, Eq2 = `Y #= bar2(X)`, is_sat(`Eq1 /\ Eq2`). ================================================ FILE: src/test/resources/test078_ok.flg ================================================ type 'a foo = | foo1(i32) | foo2('a bar) and 'a bar = | bar1('a) | bar2('a foo). rel ok. ok :- X = #x[i32 foo], Y = #y[i32 bar], Eq1 = `X #= foo1(42)`, Eq2 = `Y #= bar2(X)`, is_sat(`Eq1 /\ Eq2`). ================================================ FILE: src/test/resources/test079_ok.flg ================================================ rel ok. ok :- X = #x[i32], Eq1 = `X #= 42`, Eq2 = `X #= 99`, is_sat(Eq1), is_sat(Eq2), !is_sat(`Eq1 /\ Eq2`). ================================================ FILE: src/test/resources/test080_bd.flg ================================================ rel bad. bad :- _ = `#let 42 = 42 in 42`. ================================================ FILE: src/test/resources/test081_ok.flg ================================================ rel ok. ok :- X = #x[bool], is_sat(`#let X = true in X`), !is_sat(`#let X = false in X`), is_sat(`#let X = false in #let X = true in X`), !is_sat(`#let X = true in #let X = false in X`). ================================================ FILE: src/test/resources/test082_ok.flg ================================================ type 'a my_list = | my_nil | my_cons('a, 'a my_list). rel ok. ok :- X = #x[i32 my_list], E = `#is_my_nil(X)`, F = `#is_my_cons(X)`, is_sat(E), is_sat(F), !is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test083_ok.flg ================================================ rel ok. ok :- X = #x[i32 list], E = `#is_nil(X)`, F = `#is_cons(X)`, is_sat(E), is_sat(F), !is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test084_ok.flg ================================================ type 'a my_option = | my_none | my_some('a). rel ok. ok :- X = #x[i32 my_option], E = `#my_some_1(X) #= 0`, F = `#my_some_1(X) #= 42`, is_sat(E), is_sat(F), !is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test085_ok.flg ================================================ rel ok. ok :- X = #x[i32 option], E = `#some_1(X) #= 0`, F = `#some_1(X) #= 42`, is_sat(E), is_sat(F), !is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test086_ok.flg ================================================ rel ok. ok :- X1 = #x[i32], L1 = `[1, X1, 2]`, X2 = #x[string], L2 = `["foo", X2, "baz"]`, E = `#cons_1(#cons_2(L1)) #= 42`, F = `#cons_1(#cons_2(L2)) #= "bar"`, is_sat(E), is_sat(F), is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test087_ok.flg ================================================ rel ok. ok :- X = #x[i32], L = `[1, X, 2]`, Y = `#cons_1(#cons_2(L))`, E = `Y #= 42`, F = `Y #= -42`, G = `Y #= X`, is_sat(E), is_sat(F), !is_sat(`E /\ F`), is_valid(G). ================================================ FILE: src/test/resources/test088_ok.flg ================================================ rel ok. ok :- is_valid(`smt_eq[bool list]([], [])`). ================================================ FILE: src/test/resources/test089_ok.flg ================================================ rel ok1. ok1 :- X = #{1}[bool], Y = #{"2"}[bool], X != Y, is_sat(`X #= Y`), !is_valid(`X #= Y`). rel ok2. ok2 :- X = #{1}[bool], Y = #y[bool], X != Y, is_sat(`X #= Y`), !is_valid(`X #= Y`). rel ok3. ok3 :- is_sat(`#{1}[bool] #= #{"2"}[bool]`), !is_valid(`#{1}[bool] #= #{"2"}[bool]`). rel ok4. ok4 :- X = #{1}[bool], Y = #{1}[bool], X = Y, is_valid(`X #= Y`). rel ok5. ok5 :- is_valid(`#{1}[bool] #= #{1}[bool]`). rel ok. ok :- ok1, ok2, ok3, ok4, ok5. ================================================ FILE: src/test/resources/test090_ok.flg ================================================ type foo = a | b | c. rel common(foo, i32). common(a, 0). common(b, 1). common(c, 2). rel special(foo, i32). special(a, 5). rel op(foo, i32). op(X, Res) :- common(X, Res), X not a. op(a, Res) :- special(a, Res). rel ok. ok :- op(a, 5), !op(a, 0), !(a not a), b not a, c not a, a not b, !(b not b), c not b, a not c, b not c, !(c not c). ================================================ FILE: src/test/resources/test091_bd.flg ================================================ type foo = | a. rel ok. ok :- !a not a. ================================================ FILE: src/test/resources/test092_ok.flg ================================================ @edb rel foo(i32). fun incr(X: i32) : i32 = X + 1. fun zero : i32 = 0. foo(incr(41)). foo(zero). foo(-12 - 30). rel ok. ok :- foo(42), foo(0), foo(-42). ================================================ FILE: src/test/resources/test093_ok.flg ================================================ rel ok1. ok1 :- true. rel ok2. ok2 :- !false. rel ok3. ok3 :- X = true, X. rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test094_ok.flg ================================================ rel ok. ok :- !is_sat(`forall #x[bool] . #x[bool] #= #y[bool]`). ================================================ FILE: src/test/resources/test095_ok.flg ================================================ rel ok. ok :- !is_sat(`exists #x[bool] . ~(#x[bool] #= #x[bool])`). ================================================ FILE: src/test/resources/test096_ok.flg ================================================ rel ok ok. (* rel ok. ok :- F = `forall #x[bool] . #x[bool] #= #y[bool]`, G = substitute(#y[bool], `#x[bool]`, F), !is_sat(G). *) ================================================ FILE: src/test/resources/test097_ok.flg ================================================ rel ok ok. (* rel ok. ok :- F = `exists #x[bool] . ~(#x[bool] #= #y[bool])`, G = substitute(#y[bool], `#x[bool]`, F), is_valid(G). *) ================================================ FILE: src/test/resources/test098_bd.flg ================================================ rel foo(i32). :- foo("hello"). ================================================ FILE: src/test/resources/test099_ok.flg ================================================ rel ok. ok :- none = get_model([`#x[bool]`, `~#x[bool]`], none), some(M) = get_model([`#x[bool]`, `~#y[bool]`], none), query_model(#x[bool], M) = some(true), query_model(#y[bool], M) = some(false), query_model(#z[bool], M) = none. ================================================ FILE: src/test/resources/test100_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`(#x[i32] #= 42)`, `(#y[i32] #= bv_neg(#x[i32]))`], none), query_model(#x[i32], M) = some(42), query_model(#y[i32], M) = some(-42). ================================================ FILE: src/test/resources/test101_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[i64] #= 42L`, `#y[i64] #= bv_neg(#x[i64])`]), query_model(#x[i64], M) = some(42L), query_model(#y[i64], M) = some(-42L). ================================================ FILE: src/test/resources/test102_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[fp32] #= 42F`, `#y[fp32] #= fp_neg(#x[fp32])`], none), query_model(#x[fp32], M) = some(42F), query_model(#y[fp32], M) = some(-42F). ================================================ FILE: src/test/resources/test103_ok.flg ================================================ rel ok1. ok1 :- some(M) = get_model([`fp_is_nan(#x[fp32])`], none), query_model(#x[fp32], M) = some(fp32_nan). rel ok2. ok2 :- some(M) = get_model([`#x[fp32] #= fp32_pos_infinity`], none), query_model(#x[fp32], M) = some(fp32_pos_infinity). rel ok3. ok3 :- some(M) = get_model([`#x[fp32] #= fp32_neg_infinity`], none), query_model(#x[fp32], M) = some(fp32_neg_infinity). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test104_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`(#x[fp64] #= 42D)`, `#y[fp64] #= fp_neg(#x[fp64])`], none), query_model(#x[fp64], M) = some(42D), query_model(#y[fp64], M) = some(-42D). ================================================ FILE: src/test/resources/test105_ok.flg ================================================ rel ok1. ok1 :- some(M) = get_model([`fp_is_nan(#x[fp64])`], none), query_model(#x[fp64], M) = some(fp64_nan). rel ok2. ok2 :- some(M) = get_model([`#x[fp64] #= fp64_pos_infinity`], none), query_model(#x[fp64], M) = some(fp64_pos_infinity). rel ok3. ok3 :- some(M) = get_model([`#x[fp64] #= fp64_neg_infinity`], none), query_model(#x[fp64], M) = some(fp64_neg_infinity). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test106_ok.flg ================================================ fun msg : string = "Hello, \"world\"!\nGoodbye!". rel ok. ok :- some(M) = get_model([`#x[string] #= msg`], none), query_model(#x[string], M) = some(msg). ================================================ FILE: src/test/resources/test107_ok.flg ================================================ fun msg : string = "Hello, \"world\"!\nGoodbye!". rel ok. ok :- some(M) = get_model([`#x[bool]`, `#y[string] #= msg`, `~#z[bool]`], none), query_model(#x[bool], M) = some(true), query_model(#y[string], M) = some(msg), query_model(#z[bool], M) = some(false). ================================================ FILE: src/test/resources/test108_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#is_nil(#x[bool list])`, `~#is_nil(#y[bool list])`], none), query_model(#x[bool list], M) = some([]), query_model(#y[bool list], M) = some(_ :: _). ================================================ FILE: src/test/resources/test109_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#is_nil(#x[i32 list])`, `~#is_nil(#y[i32 list])`], none), query_model(#x[i32 list], M) = some([]), query_model(#y[i32 list], M) = some(_ :: _). ================================================ FILE: src/test/resources/test110_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[bv[16]] #= bv_const[16](42)`, `#y[i32] #= bv_to_bv_signed[16,32](#x[bv[16]])`], none), query_model(#x[bv[16]], M) = none, query_model(#y[bv[32]], M) = some(42). ================================================ FILE: src/test/resources/test111_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[bv[16]] #= bv_big_const[16](42L)`, `#y[i32] #= bv_to_bv_signed[16,32](#x[bv[16]])`], none), query_model(#x[bv[16]], M) = none, query_model(#y[bv[32]], M) = some(42). ================================================ FILE: src/test/resources/test112_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#is_nil(#x[bv[16] list])`], none), query_model(#x[bv[16] list], M) = none. ================================================ FILE: src/test/resources/test113_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[bv[16]] #= bv_const[16](-1)`, `#y[i32] #= bv_to_bv_signed[16,32](#x[bv[16]])`], none), query_model(#x[bv[16]], M) = none, query_model(#y[bv[32]], M) = some(-1). ================================================ FILE: src/test/resources/test114_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#x[bv[16]] #= bv_big_const[16](-1L)`, `#y[i32] #= bv_to_bv_signed[16,32](#x[bv[16]])`], none), query_model(#x[bv[16]], M) = none, query_model(#y[bv[32]], M) = some(-1). ================================================ FILE: src/test/resources/test115_ok.flg ================================================ (* Make sure get_model acts deterministically. *) rel ok. ok :- some(M1) = get_model([`~(#x[bv[32]] #= #y[bv[32]])`], none), some(M2) = get_model([`~(#x[bv[32]] #= #y[bv[32]])`], none), M1 = M2. ================================================ FILE: src/test/resources/test116_ok.flg ================================================ rel ok. ok :- some(M) = get_model([`#is_nil(#x[bool list])`, `#is_nil(#y[i32 list])`], none), query_model(#x[bool list], M) = some([]), query_model(#y[i32 list], M) = some([]). ================================================ FILE: src/test/resources/test117_ok.flg ================================================ rel ok. ok :- is_sat(`exists #x[bv[32]], #y[bv[16]] . bv_to_bv_signed[32,16](#x[bv[32]]) #= #y[bv[16]]`). ================================================ FILE: src/test/resources/test118_bd.flg ================================================ rel ok. ok :- is_sat(`exists #x[bv[32]], true, #y[bv[16]] . bv_to_bv_signed[32,16](#x[bv[32]]) #= #y[bv[16]]`). ================================================ FILE: src/test/resources/test119_ok.flg ================================================ rel ok. ok :- is_valid(`forall #x[bool], #x[bool] . #x[bool] #= #x[bool]`). ================================================ FILE: src/test/resources/test120_ok.flg ================================================ uninterpreted fun foo(bool smt) : bool smt. rel ok. ok :- X = #x[bool], is_valid(`(forall X : foo(X). foo(X) #= X) ==> foo(true)`). ================================================ FILE: src/test/resources/test121_ok.flg ================================================ rel ok. ok :- A = #a[(i32, i32) array], X = #x[i32], is_valid(`array_select(array_store(A, X, X), X) #= X`). ================================================ FILE: src/test/resources/test122_ok.flg ================================================ type foo = foo1 | foo2(foo, foo). rel ok. ok :- A = #a[(foo, foo) array], X = #x[foo], is_valid(`array_select(array_store(A, X, X), X) #= X`). ================================================ FILE: src/test/resources/test123_ok.flg ================================================ rel ok. ok :- A = #a[(i32, i32) array], X = #x[i32], Y = #y[i32], !is_valid(`array_select(array_store(array_store(A, X, 42), Y, 21), X) #= 42`). ================================================ FILE: src/test/resources/test124_ok.flg ================================================ rel ok. ok :- A = #a[(i32, i32) array], X = #x[i32], Y = #y[i32], is_valid(`~(X #= Y) ==> array_select(array_store(array_store(A, X, 42), Y, 21), X) #= 42`). ================================================ FILE: src/test/resources/test125_ok.flg ================================================ rel ok. ok :- I = #x[int], F = `int_add(I, int_big_const(0L)) #= int_mul(I, int_const(1))`, is_valid(F). ================================================ FILE: src/test/resources/test126_ok.flg ================================================ rel ok. ok :- X = #x[string], Y = #y[string], F = `str_prefixof(X, Y) ==> exists #i[int]. (str_substr(Y, #i[int], str_len(X)) #= X)`, is_valid(F). ================================================ FILE: src/test/resources/test127_ok.flg ================================================ uninterpreted fun foo1(bool smt) : bool smt. uninterpreted fun foo2(bool smt) : bool smt. rel ok. ok :- X = #x[bool], Y = #y[bool], Axiom = `forall X, Y : foo1(X), foo2(Y). (foo1(X) #= X) \/ (foo2(Y) #= Y)`, is_valid(`(Axiom /\ foo1(X) /\ foo2(X)) ==> X`). ================================================ FILE: src/test/resources/test128_ok.flg ================================================ rel ok ok. (* rel ok. ok :- X = #x[bool], Y = #y[bool], is_free(X, `X /\ Y`), is_free(Y, `X /\ Y`), !is_free(Y, `X`), is_free(X, `#let Y = X in Y`), !is_free(Y, `#let Y = X in Y`), is_free(X, `#let X = X in X`), is_free(X, `forall Y. X`), !is_free(X, `forall X. X`), !is_free(X, `forall X, Y. X /\ Y`), is_free(Y, `forall X, X. X /\ Y`). *) ================================================ FILE: src/test/resources/test129_ok.flg ================================================ rel ok. ok :- X = #x[bool], Y = #y[bool], is_sat_opt([`X #= Y`], some(100)) = some(true). ================================================ FILE: src/test/resources/test130_bd.flg ================================================ rel not_ok. not_ok :- X = get_model([`true`], none), _ = `X #= X`. ================================================ FILE: src/test/resources/test131_bd.flg ================================================ rel not_ok. not_ok :- X = get_model([`true`], none), _ = `X #= X`. ================================================ FILE: src/test/resources/test132_bd.flg ================================================ rel not_ok. not_ok :- X = get_model([`true`], none), Y = [none, X], _ = `#is_nil(Y)`. ================================================ FILE: src/test/resources/test133_ok.flg ================================================ @edb rel formula(bool smt). rel not_ok. not_ok :- formula(X), X = `_Y /\ _Z`. ================================================ FILE: src/test/resources/test134_ok.flg ================================================ uninterpreted sort typ. uninterpreted fun subtyp(typ smt, typ smt) : bool smt. rel ok. ok :- X = #x[typ], Refl = `forall X. subtyp(X, X)`, is_valid(`Refl ==> subtyp(X, X)`). ================================================ FILE: src/test/resources/test135_ok.flg ================================================ uninterpreted sort typ. uninterpreted fun subtyp(typ smt, typ smt) : bool smt. rel ok. ok :- X = #x[typ], Y = #y[bool], some(M) = get_model([`subtyp(X, X)`, `Y`], none), query_model(X, M) = none, query_model(Y, M) = some(true). ================================================ FILE: src/test/resources/test136_ok.flg ================================================ uninterpreted sort ('a, 'b) typ. uninterpreted fun subtyp((bool, i32) typ smt, (bool, i32) typ smt) : bool smt. rel ok. ok :- X = #x[(bool, i32) typ], Refl = `forall X. subtyp(X, X)`, is_valid(`Refl ==> subtyp(X, X)`). ================================================ FILE: src/test/resources/test137_ok.flg ================================================ uninterpreted sort ('a, 'b) typ. uninterpreted fun subtyp((bool, i32) typ smt, (bool, i32) typ smt) : bool smt. rel ok. ok :- X = #x[(bool, i32) typ], Y = #y[bool], some(M) = get_model([`subtyp(X, X)`, `Y`], none), query_model(X, M) = none, query_model(Y, M) = some(true). ================================================ FILE: src/test/resources/test138_ok.flg ================================================ fun a : ('a, i32) array smt = `array_const(0)`. rel ok. ok :- X = #x[string], Y = #y[bool], is_valid(`array_select(a, X) #= 0 /\ array_select(a, Y) #= 0`). ================================================ FILE: src/test/resources/test139_ok.flg ================================================ uninterpreted sort sv_map. uninterpreted fun f((string, i32 option) array smt) : bool smt. rel ok. ok :- is_sat(`f(array_const(none))`). ================================================ FILE: src/test/resources/test140_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test141_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test142_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test143_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test144_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test145_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test146_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test147_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test148_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test149_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test150_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test151_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test152_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test153_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test154_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test155_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). ================================================ FILE: src/test/resources/test156_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test157_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test158_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test159_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test160_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test161_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test162_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test163_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test164_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test165_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test166_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test167_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @topdown rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test168_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @topdown rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test169_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @topdown rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test170_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @topdown rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test171_ok.flg ================================================ @edb rel a(i32). @edb rel d(i32). @bottomup rel b(i32). @bottomup rel c(i32, i32). @bottomup rel p(i32). @bottomup rel q(i32). a(0). a(1). a(2). d(0). d(2). d(4). p(X) :- a(X). b(X) :- d(X). c(X, Y) :- b(X), p(X), b(Y). q(X) :- c(X, X). rel ok. ok :- q(2). :- ok. ================================================ FILE: src/test/resources/test172_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @bottomup rel a(i32, i32). @bottomup rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test173_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @topdown rel a(i32, i32). @bottomup rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test174_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @bottomup rel a(i32, i32). @topdown rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test175_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @topdown rel a(i32, i32). @topdown rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test176_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @bottomup rel a(i32, i32). @bottomup rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. :- ok. ================================================ FILE: src/test/resources/test177_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @topdown rel a(i32, i32). @bottomup rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. :- ok. ================================================ FILE: src/test/resources/test178_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @bottomup rel a(i32, i32). @topdown rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. :- ok. ================================================ FILE: src/test/resources/test179_ok.flg ================================================ @edb rel d(i32, i32). @edb rel e(i32, i32). @edb rel f(i32, i32). @topdown rel a(i32, i32). @topdown rel b(i32). e(1, 2). d(1, 2). d(3, 4). d(4, 5). d(5, 3). f(2, 1). (* Modified this slightly from example by Meskes and Noack [1993] *) b(X) :- e(Z, X), f(X, Z). a(X, Y) :- d(X, Y), !b(Y). a(X, Y) :- a(X, Z), d(Z, Y), !b(Y). @edb rel three(i32). three(3). @edb rel four(i32). four(4). @edb rel five(i32). five(5). rel ok1. ok1 :- a(3, X), three(X). rel ok2. ok2 :- a(3, X), four(X). rel ok3. ok3 :- a(3, X), five(X). rel ok. ok :- ok1, ok2, ok3. :- ok. ================================================ FILE: src/test/resources/test180_ok.flg ================================================ @edb rel foo(bool). foo(true). rel ok. ok :- if true then true else false, let X = true in X && X, match true with | true => true | false => false end, foo(if true then true else false), foo(let X = true in X && X), foo(match true with | true => true | false => false end). ================================================ FILE: src/test/resources/test181_ok.flg ================================================ @edb rel foo(bool) foo(true). rel ok. ok :- F = false, T = true, if T then T else F, match true with X => X && T end, let X = true in X && T, match T with | true => T | false => F end, foo(if T then T else F), foo(let X = true in X && T), foo(match T with | true => T | false => F end). ================================================ FILE: src/test/resources/test182_ok.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- X = { val = 42 ; next = none ; }, Y = { val = 21 ; next = some(X) ; }, some(Z) = next(Y), 42 = val(Z). ================================================ FILE: src/test/resources/test183_ok.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- Y = { val = 21 ; next = some ({ val = 42 ; next = none }) }, some(Z) = next(Y), 42 = val(Z). ================================================ FILE: src/test/resources/test184_ok.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- X = { val = 42 ; next = none ; }, Y = { val = "hello" ; next = none ; }, next(X) = none, next(Y) = none. ================================================ FILE: src/test/resources/test185_bd.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- X = { val = 42 ; next = none ; }, Y = { val = "hello" ; next = some(X) ; }, some(Z) = next(Y), 42 = val(Z). ================================================ FILE: src/test/resources/test186_ok.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- X = { val = 21 ; next = some({ val = 33 ; next = none }) ; }, Y = { X with next = some({ val = 42 ; next = none }) }, some(Z) = next(Y), 42 = val(Z). ================================================ FILE: src/test/resources/test187_ok.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- _X = { val = 21 ; next = some({ val = 33 ; next = none }) ; }, Y = { _X with next = some({ val = 42 ; next = none }) ; val = 0 }, some(Z) = next(Y), 42 = val(Z), 0 = val(Y). ================================================ FILE: src/test/resources/test188_bd.flg ================================================ type 'a linked_list = { val : 'a ; next : 'a linked_list option ; }. rel ok. ok :- X = { val = 21 ; next = some({ val = 33 ; next = none }) ; }, Y = { X with next = some({ val = "hello" ; next = none }) }, some(Z) = next(Y), 42 = val(Z). ================================================ FILE: src/test/resources/test189_ok.flg ================================================ type point2d = { x : i32; y : i32; }. rel ok. ok :- P = #p[point2d], E = `#x(P) #= #y(P) /\ #x(P) #= 0`, is_sat(E), F = `#y(P) #= 42`, is_sat(F), !is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test190_ok.flg ================================================ type point2d = { x : i32; y : i32; }. rel ok. ok :- P = #p[point2d], E = [`#x(P) #= #y(P)`, `#x(P) #= 0`], some(M) = get_model(E, none), some(P2) = query_model(P, M), y(P2) = 0. ================================================ FILE: src/test/resources/test191_input/complex_terms.tsv ================================================ my_cons("jimmy", my_nil) my_cons("hendrix", my_nil) my_cons("giuseppe", my_nil) my_cons("garibaldi", my_nil) ================================================ FILE: src/test/resources/test191_input/names.tsv ================================================ "jimmy" "hendrix" "giuseppe" "garibaldi" ================================================ FILE: src/test/resources/test191_input/numbers.tsv ================================================ 1 2 3 4 5 6 7 8 9 ================================================ FILE: src/test/resources/test191_ok.flg ================================================ @disk @edb rel names(string, string). @disk @edb rel numbers(i32, i32, i32). numbers(100, 200, 300). type 'a my_list = | my_nil | my_cons('a, 'a my_list). @disk @edb rel complex_terms(string my_list, string my_list). rel ok1. ok1 :- names("jimmy", "hendrix"), names("giuseppe", "garibaldi"). fun singleton(X: 'a) : 'a my_list = my_cons(X, my_nil). rel ok2. ok2 :- complex_terms(singleton("jimmy"), singleton("hendrix")), complex_terms(singleton("giuseppe"), singleton("garibaldi")). rel ok3. ok3 :- numbers(1, 2, 3), numbers(4, 5, 6), numbers(7, 8, 9), numbers(100, 200, 300). rel ok. ok :- ok1, ok2, ok3. ================================================ FILE: src/test/resources/test192_ok.flg ================================================ rel ok. ok :- X = #{"x"}[i32], Y = #{42}[i32], Z = #{("x", 42)}[i32], X != Y, X != Z, Y != X, Y != Z, Z != X, Z != Y. ================================================ FILE: src/test/resources/test193_ok.flg ================================================ @edb rel nums(i32, i32, i32) nums(1, 2, 3). nums(4, 3, 1). nums(1, 3, 6). @edb rel num(i32) num(6). num(12). num(18). @edb rel empty @edb rel full full. fun sum(Xs: i32 list) : i32 = match Xs with | [] => 0 | X :: Xs => X + sum(Xs) end fun mulAndSum(Xs: (i32 * i32 * i32) list) : i32 = match Xs with | [] => 0 | (X1, X2, X3) :: Xs => X1 * X2 * X3 + mulAndSum(Xs) end rel agg1(i32) agg1(X) :- X = sum(nums(_1, ??, _2)). rel agg2(i32) agg2(X) :- X = sum(nums(_1, 3, ??)). rel ok ok :- sum(num(??)) = 36, mulAndSum(nums(??, ??, ??)) = 36, agg1(8), agg2(7), !empty, full. ================================================ FILE: src/test/resources/test217_bd.flg ================================================ @topdown rel foo rel bar fun f : bool = foo bar :- f. ================================================ FILE: src/test/resources/test218_bd.flg ================================================ rel foo rel bar fun f : bool = foo bar :- f. :- bar. ================================================ FILE: src/test/resources/test219_ok.flg ================================================ @bottomup rel foo rel bar fun f : bool = foo bar :- f. :- bar. ================================================ FILE: src/test/resources/test220_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- !q, r1, r2, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. ================================================ FILE: src/test/resources/test221_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, !q, r2, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. ================================================ FILE: src/test/resources/test222_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, !q, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. ================================================ FILE: src/test/resources/test223_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, r3, !q. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. ================================================ FILE: src/test/resources/test224_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- !q, r1, r2, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. :- ok. ================================================ FILE: src/test/resources/test225_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, !q, r2, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. :- ok. ================================================ FILE: src/test/resources/test226_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, !q, r3. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. :- ok. ================================================ FILE: src/test/resources/test227_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, r3, !q. q :- s. r1 :- p. r2 :- p. r3 :- p. s :- p. r1. r2. r3. s. rel ok ok :- p. :- ok. ================================================ FILE: src/test/resources/test228_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- !q, r1, r2, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. ================================================ FILE: src/test/resources/test229_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, !q, r2, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. ================================================ FILE: src/test/resources/test230_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, !q, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. ================================================ FILE: src/test/resources/test231_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, r3, !q. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. ================================================ FILE: src/test/resources/test232_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- !q, r1, r2, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. :- ok. ================================================ FILE: src/test/resources/test233_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, !q, r2, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. :- ok. ================================================ FILE: src/test/resources/test234_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, !q, r3. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. :- ok. ================================================ FILE: src/test/resources/test235_ok.flg ================================================ rel p rel q rel r1 rel r2 rel r3 rel s p :- r1, r2, r3, !q. q :- p. r1 :- p. r2 :- p. r3 :- p. s :- p. r1 :- s. r2 :- s. r3 :- s. s. q. rel ok ok :- !p. :- ok. ================================================ FILE: src/test/resources/test236_ok.flg ================================================ rel q. q :- !q. rel ok. ok :- q. ================================================ FILE: src/test/resources/test237_ok.flg ================================================ rel q. q :- !q. rel ok. ok :- q. :- ok. ================================================ FILE: src/test/resources/test238_ok.flg ================================================ @edb rel a(i32, i32) @edb rel b(i32, i32) rel p(i32, i32) a(1, 2). a(1, 3). p(X, Y) :- a(X, Y). rel not_ok not_ok :- !a(_, _). not_ok :- !a(_, 2). not_ok :- !a(_, 3). not_ok :- !p(_, _). not_ok :- !p(_, 2). not_ok :- !p(_, 3). rel ok ok :- !b(_, _), !a(2, _), !p(2, _), !not_ok. ================================================ FILE: src/test/resources/test240_ok.flg ================================================ @edb rel foo(i32 smt) foo(`bv_to_bv_unsigned[64,32](0L)`). rel ok ok :- foo(X), is_valid(`X #= 0`). ================================================ FILE: src/test/resources/test241_ok.flg ================================================ @edb rel foo(i32 smt) foo(let X = `bv_to_bv_unsigned[64,32](0L)` in X). rel ok ok :- foo(X), is_valid(`X #= 0`). ================================================ FILE: src/test/resources/test242_ok.flg ================================================ rel ok ok :- Y = `bv_to_bv_unsigned[64,32](0L)`, is_valid(`Y #= 0`). ================================================ FILE: src/test/resources/test243_ok.flg ================================================ rel ok ok :- Y = let X = `bv_to_bv_unsigned[64,32](0L)` in X, is_valid(`Y #= 0`). ================================================ FILE: src/test/resources/test244_ok.flg ================================================ fun foo : i32 smt = `bv_to_bv_unsigned[64,32](0L)` rel ok ok :- foo = X, is_valid(`X #= 0`). ================================================ FILE: src/test/resources/test245_ok.flg ================================================ fun foo : i32 smt = let X = `bv_to_bv_unsigned[64,32](0L)` in X rel ok ok :- foo = X, is_valid(`X #= 0`). ================================================ FILE: src/test/resources/test248_bd.flg ================================================ fun foo(X: i32) : i32 = X fun bar : i32 = foo(_X) ================================================ FILE: src/test/resources/test249_ok.flg ================================================ @edb rel ok ok. :- ok. ================================================ FILE: src/test/resources/test250_ok.flg ================================================ @topdown rel foo(i32) foo(0). foo(42). rel ok ok :- foo(42), foo(_). ================================================ FILE: src/test/resources/test251_bd.flg ================================================ @topdown rel foo(i32) foo(_X). fun bar(X: i32) : bool = foo(X) ================================================ FILE: src/test/resources/test252_ok.flg ================================================ @bottomup rel foo(i32) foo(42). fun bar(X: i32) : bool = foo(X) rel ok ok :- bar(42). :- ok. ================================================ FILE: src/test/resources/test253_ok.flg ================================================ rel foo(i32) foo(42). fun bar(X: i32) : bool = foo(X) @topdown rel baz(i32) baz(X) :- foo(X). rel ok ok :- baz(42). ================================================ FILE: src/test/resources/test254_ok.flg ================================================ rel ok ok :- X = #x[string], some(_) = get_model([`X #= "!@#$%^&*-+/"`], none). ================================================ FILE: src/test/resources/test255_bd.flg ================================================ fun foo(X: 'a list) : 'a list = match X with | X :: X :: _ => [X, X] | _ => [] end ================================================ FILE: src/test/resources/test256_ok.flg ================================================ (* Test to make sure that dependencies in the heads of rules are being picked up. *) rel bar bar. rel baz baz. rel woo woo. rel foo1(bool) rel foo2(bool) rel foo3(bool) rel foo4(bool) rel foo5(bool) foo1(bar && baz && woo). foo2(bar && baz && woo). foo3(bar && baz && woo). foo4(bar && baz && woo). foo5(bar && baz && woo). rel ok ok :- foo1(true), foo2(true), foo3(true), foo4(true), foo5(true), true. ================================================ FILE: src/test/resources/test257_ok.flg ================================================ @edb rel foo(string) foo("\n"). foo("\r"). foo("\t"). foo("\""). foo("\\"). foo("\\\""). foo("\"\\"). foo("\\\"\\"). foo("\"\\\""). @edb rel bar(string list) bar(["\"", "\\"]). bar(["\\", "\""]). bar(["\"", "\\", "\""]). bar(["\\", "\"", "\\"]). ================================================ FILE: src/test/resources/test258_ok.flg ================================================ (* This is a weird test... basically trying to make sure that the parser rejects the string below. If the parser does so, it apparently skips that line, and so `foo` is empty. *) @edb rel foo(string) foo("\\""). rel ok ok :- !foo(_). ================================================ FILE: src/test/resources/test259_ok.flg ================================================ @topdown rel mem(i32, i32 list) mem(X, X :: _). mem(X, _ :: Xs) :- mem(X, Xs). rel ok. ok :- mem(X, [1, 2, 3]), X = 3. ================================================ FILE: src/test/resources/test260_ok.flg ================================================ rel foo rel ok ok :- !foo. ================================================ FILE: src/test/resources/test261_ok.flg ================================================ @edb rel foo(fp32) foo(1e-1f). foo(1.0e-2f). foo(1e1f). foo(1.0e2f). rel ok. ok :- foo(0.01f), foo(0.1f), foo(10f), foo(100f). ================================================ FILE: src/test/resources/test262_ok.flg ================================================ @edb rel foo(fp32) foo(1e-1F). foo(1.0e-2F). foo(1e1F). foo(1.0e2F). rel ok. ok :- foo(0.01F), foo(0.1F), foo(10F), foo(100F). ================================================ FILE: src/test/resources/test263_ok.flg ================================================ @edb rel foo(fp64) foo(1e-1d). foo(1.0e-2d). foo(1e1d). foo(1.0e2d). rel ok. ok :- foo(0.01d), foo(0.1d), foo(10d), foo(100d). ================================================ FILE: src/test/resources/test264_ok.flg ================================================ @edb rel foo(fp64) foo(1e-1D). foo(1.0e-2D). foo(1e1D). foo(1.0e2D). rel ok. ok :- foo(0.01D), foo(0.1D), foo(10D), foo(100D). ================================================ FILE: src/test/resources/test265_ok.flg ================================================ @edb rel foo(fp64) foo(1e-1). foo(1.0e-2). foo(1e1). foo(1.0e2). rel ok. ok :- foo(0.01), foo(0.1), foo(10d), foo(100D). ================================================ FILE: src/test/resources/test266_bd.flg ================================================ rel foo(i32 list) rel ok. ok :- _X = foo([??]). ================================================ FILE: src/test/resources/test267_ok.flg ================================================ rel ok. ok :- match (true, false) with (_, _) => true end. ================================================ FILE: src/test/resources/test268_bd.flg ================================================ fun make_eq(X : 'a list smt, Y : 'a) : bool smt = `#cons_1(#cons_2(X)) #= Y`. rel ok. ok :- X1 = #x[i32], L1 = `[1, X1, 2]`, X2 = #x[string], L2 = `["foo", X2, "baz"]`, E = make_eq(L1, 42), F = make_eq(L2, "bar"), is_sat(E), is_sat(F), is_sat(`E /\ F`). ================================================ FILE: src/test/resources/test269_bd.flg ================================================ rel ok. ok :- is_valid(`[] #= []`). ================================================ FILE: src/test/resources/test270_bd.flg ================================================ rel ok. ok :- is_valid(`smt_eq['a list list]([], [])`). ================================================ FILE: src/test/resources/test271_bd.flg ================================================ rel ok. ok :- is_valid(`smt_eq[i32 smt list list]([], [])`). ================================================ FILE: src/test/resources/test272_bd.flg ================================================ rel ok. ok :- is_valid(`smt_eq[i32 sym list list]([], [])`). ================================================ FILE: src/test/resources/test273_ok.flg ================================================ rel ok ok :- is_sat(`(4 #= 42) #= ("hello" #= "goodbye")`). ================================================ FILE: src/test/resources/test274_ok.flg ================================================ @edb rel phi(bool smt) phi(`false #= true`). phi(`"hello" #= "hello"`). rel ok ok :- phi(`X #= _Y`), is_valid(`~X`). ================================================ FILE: src/test/resources/test275_ok.flg ================================================ @edb rel phi(bool smt) phi(`false #= true`). phi(`"hello" #= "hello"`). rel ok ok :- phi(`smt_eq[bool](X, _Y)`), is_valid(`~X`). ================================================ FILE: src/test/resources/test276_inputA/foo.tsv ================================================ 0 ================================================ FILE: src/test/resources/test276_inputB/foo.tsv ================================================ 42 ================================================ FILE: src/test/resources/test276_ok.flg ================================================ @disk @edb rel foo(i32) rel ok ok :- foo(0), foo(42). ================================================ FILE: src/test/resources/test277_ok.flg ================================================ rel test(i32) test(1) :- string_matches("hello", "hello"). test(2) :- string_matches("hello", ".*el.*"). test(3) :- string_matches("hello", "he.*o"). test(4) :- string_matches("hello", "^h.*o"). test(5) :- string_matches("hello", "h.*o$"). test(6) :- !string_matches("hello", "^e.*o"). test(7) :- !string_matches("hello", "h.*l$"). test(8) :- string_starts_with("hello", ""). test(9) :- string_starts_with("hello", "he"). test(10) :- string_starts_with("hello", "hello"). test(11) :- !string_starts_with("hello", "hello!"). test(12) :- !string_starts_with("hello", "el"). rel ok ok :- test(1), test(2), test(3), test(4), test(5), test(6), test(7), test(8), test(9), test(10), test(11), test(12). ================================================ FILE: src/test/resources/test278_ok.flg ================================================ @edb rel foo(bool sym) foo(#{41}[bool]). foo(#hello[bool]). rel ok ok :- foo(#{X}[bool]), X + 1 = 42. ================================================ FILE: src/test/resources/test279_ok.flg ================================================ fun foo(X: i32) : i32 = let fun bar(Y: i32) : i32 = Y + X in bar(3) rel ok ok :- foo(2) = 5. ================================================ FILE: src/test/resources/test280_ok.flg ================================================ fun foo(X1: i32, X2: i32) : i32 = let fun bar(Y: i32) : i32 = Y + X1 in let fun baz(Z: i32) : i32 = bar(Z) + X2 in baz(3) rel ok ok :- foo(1, 2) = 6. ================================================ FILE: src/test/resources/test281_ok.flg ================================================ fun foo(X1: i32, X2: i32) : i32 = let fun bar(Z: i32) : i32 = Z * X1 * baz(0) and baz(Z: i32) : i32 = if Z = 0 then 1 else X2 * bar(Z) in baz(3) rel ok ok :- foo(2, 4) = 24. ================================================ FILE: src/test/resources/test282_ok.flg ================================================ fun foo(X1: i32, X2: i32) : i32 = let fun bar(Z: i32) : i32 = Z * X1 * baz(0, 1) and baz(Z: i32, X1: i32) : i32 = if Z = 0 then X1 else X2 * bar(Z) in baz(3, 1) rel ok ok :- foo(2, 4) = 24. ================================================ FILE: src/test/resources/test283_ok.flg ================================================ fun sum(X: i32, Y: i32) : i32 = X + Y fun foo(Xs: i32 list) : i32 = fold[sum](0, Xs) rel ok ok :- foo([1, 2, 3]) = 6. ================================================ FILE: src/test/resources/test284_ok.flg ================================================ fun add1(Xs: i32 list, Y: i32) : i32 list = (Y + 1) :: Xs fun foo(Xs: i32 list) : i32 list = fold[add1]([], Xs) rel ok ok :- foo([1, 2, 3]) = [4, 3, 2]. ================================================ FILE: src/test/resources/test285_ok.flg ================================================ fun add1(Xs: i32 list, Y: i32) : i32 list = (Y + 1) :: Xs fun cons_wrapper(Xs: 'a list, X: 'a) : 'a list = X :: Xs fun foo(Xs: i32 list) : i32 list = fold[cons_wrapper]([], fold[add1]([], Xs)) rel ok ok :- foo([1, 2, 3]) = [2, 3, 4]. ================================================ FILE: src/test/resources/test286_ok.flg ================================================ fun foo(Xs: i32 list) : i32 list = let fun add1(Xs: i32 list, Y: i32) : i32 list = (Y + 1) :: Xs in let fun cons_wrapper(Xs: i32 list, X: i32) : i32 list = X :: Xs in fold[cons_wrapper]([], fold[add1]([], Xs)) rel ok ok :- foo([1, 2, 3]) = [2, 3, 4]. ================================================ FILE: src/test/resources/test287_ok.flg ================================================ fun rev(Xs: 'a list) : 'a list = let fun cons_wrapper(Ys: 'a list, X: 'a) : 'a list = X :: Ys in fold[cons_wrapper]([], Xs) rel ok ok :- rev([1, 2, 3]) = [3, 2, 1]. ================================================ FILE: src/test/resources/test288_ok.flg ================================================ fun foo(X: i32) : i32 = let fun id(Y: 'a) : 'a = Y in id(X) rel ok ok :- foo(42) = 42. ================================================ FILE: src/test/resources/test289_ok.flg ================================================ fun foo(X: string, Y: i32) : string = let fun id(Y: 'a) : 'a = Y in string_concat(id(X), to_string(id(Y))) rel ok ok :- foo("hello #", 42) = "hello #42". ================================================ FILE: src/test/resources/test290_ok.flg ================================================ fun addN(Xs: i32 list, N: i32) : i32 list = let fun rev(Xs: 'a list) : 'a list = let fun cons_wrapper(Ys: 'a list, X: 'a) : 'a list = X :: Ys in fold[cons_wrapper]([], Xs) in let fun addN(Xs: i32 list, X: i32) : i32 list = (X + N) :: Xs in rev(fold[addN]([], Xs)) rel ok ok :- addN([1, 2, 3], 10) = [11, 12, 13]. ================================================ FILE: src/test/resources/test291_ok.flg ================================================ fun foo(Xs: i32 list, M: i32, B: i32) : i32 list = let fun rev(Xs: 'a list) : 'a list = let fun cons_wrapper(Ys: 'a list, X: 'a) : 'a list = X :: Ys in fold[cons_wrapper]([], Xs) in let fun bar(Xs: i32 list, X: i32) : i32 list = (M * X + B) :: Xs in rev(fold[bar]([], Xs)) rel ok ok :- foo([1, 2, 3], 2, 1) = [3, 5, 7]. ================================================ FILE: src/test/resources/test292_ok.flg ================================================ fun foo(Xs: i32 list, M: i32, B: i32) : i32 list = let fun rev(Xs: 'a list) : 'a list = let fun cons_wrapper(Ys: 'a list, X: 'a) : 'a list = X :: Ys in fold[cons_wrapper]([], Xs) in let fun bar(Xs: i32 list, X: i32) : i32 list = baz(M * X) :: Xs and baz(X: i32) : i32 = X + B in rev(fold[bar]([], Xs)) rel ok ok :- foo([1, 2, 3], 2, 1) = [3, 5, 7]. ================================================ FILE: src/test/resources/test293_ok.flg ================================================ fun foo : i32 * i32 list = (12, [24]) ================================================ FILE: src/test/resources/test294_ok.flg ================================================ fun foo : (i32 * i32) list = [(12, 24)] ================================================ FILE: src/test/resources/test295_bd.flg ================================================ fun foo(X: 'a) : 'a smt = `X` rel not_ok not_ok :- some(M) = get_model([`true`], none), foo(M) = _. ================================================ FILE: src/test/resources/test296_bd.flg ================================================ type foo = bar(i32) @edb rel baz(foo smt) baz(`bar(#x[i32])`). rel not_ok not_ok :- baz(`bar(X)`), X + 1 = 42. ================================================ FILE: src/test/resources/test297_ok.flg ================================================ type foo = bar(i32) @edb rel baz(foo smt) baz(`bar(#x[i32])`). baz(`bar(bv_const(41))`). @edb rel buz(i32) buz(41). buz(42). rel ok ok :- buz(X), baz(`bar(bv_const(X))`), X + 1 = 42. ================================================ FILE: src/test/resources/test298_ok.flg ================================================ rel ok ok :- string_concat("", "hello") = "hello", string_concat("hel", "lo") = "hello", string_concat("", "hello") = "hello". ================================================ FILE: src/test/resources/test299_ok.flg ================================================ rel ok ok :- string_cmp("a", "bc") = cmp_lt, string_cmp("abc", "abc") = cmp_eq, string_cmp("bc", "a") = cmp_gt. ================================================ FILE: src/test/resources/test300_ok.flg ================================================ rel ok ok :- to_string(0) = "0", to_string("0") = "0", to_string(some(0)) = "some(0)". ================================================ FILE: src/test/resources/test301_ok.flg ================================================ rel ok ok :- X = #x[bool], Y = #y[bool], is_sat(`some(X) #= some(Y)`), is_sat(`~(some(X) #= some(Y))`). ================================================ FILE: src/test/resources/test302_bd.flg ================================================ rel not_ok not_ok :- X = #x[4], Y = #x[4], X = Y. ================================================ FILE: src/test/resources/test303_ok.flg ================================================ rel ok ok :- is_valid(`#x[?] ==> #{"x"}[?]`). ================================================ FILE: src/test/resources/test304_ok.flg ================================================ rel ok ok :- X = `#x[?] #= 42`, Y = `#y[?] #= 21`, Z = `#x[bv[32]] #= #y[?]`, is_sat_opt([X], none) = some(true), is_sat_opt([Y], none) = some(true), is_sat_opt([Z], none) = some(true), is_sat_opt([X, Y], none) = some(true), is_sat_opt([X, Z], none) = some(true), is_sat_opt([Y, Z], none) = some(true), is_sat_opt([X, Y, Z], none) = some(false). ================================================ FILE: src/test/resources/test305_ok.flg ================================================ rel ok ok :- is_sat_opt([`true`], none) = some(true), is_sat_opt([`#x[?] #= true`, `true`], none) = some(true), is_sat_opt([`#x[bool] #= #y[?]`, `#x[?] #= true`, `true`], none) = some(true), is_sat_opt([`#y[?] #= false`, `#x[bool] #= #y[?]`, `#x[?] #= true`, `true`], none) = some(false), is_sat_opt([`#y[?] /\ #x[?]`, `#x[bool] #= #y[?]`, `#x[?] #= true`, `true`], none) = some(true), is_sat_opt([`#x[?] /\ #x[?]`, `#x[?] #= true`, `true`], none) = some(true), is_sat_opt([`false`], none) = some(false). ================================================ FILE: src/test/resources/test306_ok.flg ================================================ fun f(X: i32, Y: i32) : i32 = let fun g(Z: i32) : i32 = let fun h(W: i32) : i32 = W + Z in h(Z) in g(X + Y) rel ok ok :- f(2, 3) = 10. ================================================ FILE: src/test/resources/test307_ok.flg ================================================ @edb rel edge(i32, i32, bool smt) edge(3, 0, `array_select[bv[4]](array_store(#C[(bv[4], bool) array], #B[bv[4]], #E[bool]), bv_big_const[4](-39L))`). edge(3, 9, `smt_eq[(bv[4], bv[4]) array](array_store(array_store(#A[(bv[4], bv[4]) array], bv_const[4](6), array_select[bv[4]](array_store(#A[(bv[4], bv[4]) array], bv_const[4](-49), #C[bv[4]]), #A[bv[4]])), #D[bv[4]], array_select[bv[8]](array_store(#E[(bv[8], bv[4]) array], #D[bv[8]], #A[bv[4]]), bv_or(bv_and(bv_big_const[8](-10L), #A[bv[8]]), bv_big_const[8](45L)))), array_store(array_store(array_store(array_store(#D[(bv[4], bv[4]) array], #D[bv[4]], #B[bv[4]]), bv_big_const[4](-19L), bv_to_bv_signed[8,4](#D[bv[8]])), bv_mul(bv_big_const[4](-38L), bv_srem(#D[bv[4]], #A[bv[4]])), bv_add(bv_big_const[4](6L), bv_xor(bv_const[4](24), #E[bv[4]]))), #A[bv[4]], bv_add(#B[bv[4]], bv_const[4](-9))))`). fun z : (bv[4], bv[8]) array smt = `array_store( #E[(bv[4], bv[8]) array], array_select[bv[8]](#D[(bv[8], bv[4]) array], bv_const[8](1)), array_select[bv[8]](#C[(bv[8], bv[8]) array], bv_const[8](2)))` ================================================ FILE: src/test/resources/test308_ok.flg ================================================ rel ok (* Check that negative integers are serialized correctly to SMT-LIB (especially for less forgiving solvers like CVC4). *) ok :- is_sat(`#x[int] #= int_const(-1)`). ================================================ FILE: src/test/resources/test309_ok.flg ================================================ fun foo(Xs: i32 list) : i32 = match Xs with | [1, 2] => 0 | [X, 3] => X | [1, 3] => 2 end rel ok ok :- foo([1, 3]) = 1. ================================================ FILE: src/test/resources/test310_ok.flg ================================================ fun foo(Xs: i32 list) : i32 = match Xs with | [1, 2] => 0 | [2, 3] => 1 | [1, 3] => 2 end rel ok ok :- foo([1, 3]) = 2. ================================================ FILE: src/test/resources/test311_ok.flg ================================================ rel ok1 rel ok2 rel ok3 rel ok4 rel ok5 rel ok ok1 :- string_to_list("") = [], string_to_list("a") = [97], string_to_list("abc") = [97, 98, 99]. ok2 :- list_to_string([]) = "", list_to_string([97]) = "a", list_to_string([97, 98, 99]) = "abc". ok3 :- char_at("", 0) = none, char_at("a", 0) = some(97), char_at("a", 1) = none, char_at("abc", 0) = some(97), char_at("abc", 1) = some(98), char_at("abc", 2) = some(99), char_at("abc", 3) = none, char_at("abc", 42) = none, char_at("abc", -42) = none. ok4 :- substring("", 0, 0) = some(""), substring("", 0, 1) = none, substring("", -1, 1) = none, substring("abc", 0, 0) = some(""), substring("abc", 0, 1) = some("a"), substring("abc", 0, 2) = some("ab"), substring("abc", 0, 3) = some("abc"), substring("abc", 0, 4) = none. ok5 :- string_length("") = 0, string_length("a") = 1, string_length("abc") = 3. ok :- ok1, ok2, ok3, ok4, ok5. ================================================ FILE: src/test/resources/test312_bd.flg ================================================ rel not_ok not_ok :- `bv_extract[32,16](42, #x[i32], 15)` = _. ================================================ FILE: src/test/resources/test313_bd.flg ================================================ rel not_ok not_ok :- `bv_extract[32,16](42, 0, #x[i32])` = _. ================================================ FILE: src/test/resources/test314_bd.flg ================================================ rel not_ok not_ok :- `int_const(#x[i32])` = _. ================================================ FILE: src/test/resources/test315_bd.flg ================================================ rel not_ok not_ok :- `int_big_const(#x[i64])` = _. ================================================ FILE: src/test/resources/test316_ok.flg ================================================ rel ok1 rel ok2 rel ok3 rel ok4 rel ok5 rel ok ok1 :- X = 42, match `X` with | bv_const(42) => true | _ => false end. ok2 :- X = 42L, match `X` with | bv_big_const(42L) => true | _ => false end. ok3 :- X = 42F, match `X` with | fp_const(42F) => true | _ => false end. ok4 :- X = 42D, match `X` with | fp_big_const(42D) => true | _ => false end. ok5 :- X = "hello", match `X` with | `"hello"` => true | _ => false end. ok :- ok1, ok2, ok3, ok4, ok5. ================================================ FILE: src/test/resources/test317_ok.flg ================================================ rel ok1 rel ok2 rel ok3 rel ok4 rel ok5 rel ok ok1 :- X = 42, match bv_const(X) with | `42` => true | _ => false end. ok2 :- X = 42L, match bv_big_const(X) with | `42L` => true | _ => false end. ok3 :- X = 42F, match fp_const(X) with | `42F` => true | _ => false end. ok4 :- X = 42D, match fp_big_const(X) with | `42D` => true | _ => false end. ok5 :- X = "hello", match `X` with | `"hello"` => true | _ => false end. ok :- ok1, ok2, ok3, ok4, ok5. ================================================ FILE: src/test/resources/test318_ok.flg ================================================ rel ok @edb rel foo(i32 smt) foo(`42`). ok :- foo(`42`), foo(`bv_const(42)`), foo(X), X = `42`, X = `bv_const(42)`, X = `bv_const(Y)`, Y = 42. ================================================ FILE: src/test/resources/test319_ok.flg ================================================ rel ok1 rel ok2 rel ok3 rel ok4 rel ok5 rel ok ok1 :- match (let x = 42 in `x`) with | `bv_const(42)` => true | _ => false end. ok2 :- match (let x = 42L in `x`) with | `bv_big_const(42L)` => true | _ => false end. ok3 :- match (let x = 42F in `x`) with | `fp_const(42F)` => true | _ => false end. ok4 :- match (let x = 42D in `x`) with | `fp_big_const(42D)` => true | _ => false end. ok5 :- match (let x = "hello" in `x`) with | `"hello"` => true | _ => false end. ok :- ok1, ok2, ok3, ok4, ok5. ================================================ FILE: src/test/resources/test320_ok.flg ================================================ (* This recreates a bug having to do with let fun expressions inside match expressions. *) rel ok fun foo(x: 'a) : 'a = match true with | true => let fun id(x: 'a) : 'a = x in id(x) end ok :- foo(42) = 42. ================================================ FILE: src/test/resources/test321_ok.flg ================================================ (* This makes sure that variable usage checks work correctly in the presence of rules with multiple head predicates. *) @edb rel foobar(i32, string) foobar(42, "hello"). rel foo(i32) rel bar(string) foo(X), bar(Y) :- foobar(X, Y). rel ok ok :- foo(42), bar("hello"). ================================================ FILE: src/test/resources/test322_bd.flg ================================================ (* This makes sure that variable usage checks work correctly in the presence of rules with multiple head predicates. *) @edb rel foobarbaz(i32, string, i32) rel foo(i32) rel bar(string) foo(X), bar(Y) :- foobarbaz(X, Y, Z). ================================================ FILE: src/test/resources/test323_ok.flg ================================================ @edb rel foo(arg1: i32, arg2: i32, foo: i32) rel bar(foo: i32, list: i32 list) foo(1, 2, 3). bar(1, []). rel ok ok :- foo(1, 2, 3), bar(1, []). ================================================ FILE: src/test/resources/test324_ok.flg ================================================ rel ok ok :- string_to_i32("42") = some(42), string_to_i32("+42") = some(42), string_to_i32("-42") = some(-42), string_to_i32("0x2a") = some(42), string_to_i32("0xffffffff") = some(-1), string_to_i32("42f") = none, string_to_i32("42d") = none, string_to_i32("42l") = none, string_to_i32("42F") = none, string_to_i32("42D") = none, string_to_i32("42L") = none, string_to_i32("42.") = none, string_to_i32("0x100000000") = none. ================================================ FILE: src/test/resources/test325_ok.flg ================================================ rel ok ok :- string_to_i64("42") = some(42L), string_to_i64("+42") = some(42L), string_to_i64("-42") = some(-42L), string_to_i64("0x2a") = some(42L), string_to_i64("0xffffffffffffffff") = some(-1L), string_to_i64("42f") = none, string_to_i64("42d") = none, string_to_i64("42l") = none, string_to_i64("42F") = none, string_to_i64("42D") = none, string_to_i64("42L") = none, string_to_i64("42.") = none, string_to_i64("0x10000000000000000") = none. ================================================ FILE: src/test/resources/test326_ok.flg ================================================ rel empty_ok rel plus_ok rel minus_ok rel union_ok rel diff_ok rel singleton_ok rel choose_ok rel size_ok rel subset_ok rel ok fun opaque_set_id(s: 'a opaque_set) : 'a opaque_set = s fun from_list(xs: 'a list) : 'a opaque_set = match xs with | [] => opaque_set_empty | h :: t => opaque_set_plus(h, from_list(t)) end empty_ok :- opaque_set_empty = opaque_set_id(opaque_set_empty). plus_ok :- from_list([1, 2, 3]) = from_list([3, 1, 2]), from_list([1, 2, 3]) != from_list([1, 2]). minus_ok :- opaque_set_minus(2, from_list([1, 2, 3])) = from_list([1, 3]). union_ok :- opaque_set_union(from_list([1, 2]), from_list([3, 4])) = from_list([1, 2, 3, 4]). diff_ok :- opaque_set_diff(from_list([1, 2, 3, 4]), from_list([2, 3])) = from_list([1, 4]). singleton_ok :- opaque_set_singleton(42) = from_list([42]). choose_ok :- S1 = from_list([1, 2]), some((X, S2)) = opaque_set_choose(S1), some((Y, S3)) = opaque_set_choose(S2), none = opaque_set_choose(S3), X != Y, X = 1 || Y = 1, X = 2 || Y = 2. size_ok :- opaque_set_size(opaque_set_empty) = 0, opaque_set_size(opaque_set_singleton("hello")) = 1, opaque_set_size(from_list([1, 2, 3, 4])) = 4. subset_ok :- opaque_set_subset(from_list([2, 4]), from_list([1, 2, 3, 4])), !opaque_set_subset(from_list([2, 4]), from_list([1, 2, 3, 5])). ok :- empty_ok, plus_ok, minus_ok, union_ok, diff_ok, singleton_ok, choose_ok, size_ok, subset_ok, true. ================================================ FILE: src/test/resources/test327_bd.flg ================================================ rel not_ok not_ok :- S = opaque_set_singleton(42), is_sat(`S #= S`). ================================================ FILE: src/test/resources/test328_ok.flg ================================================ rel ok ok :- F = [`#x[?] #= 42`, `#y[?] #= 13`], is_set_sat(opaque_set_from_list(F), none) = some(true), is_set_sat(opaque_set_from_list(`#x[i32] #= #y[?]` :: F), none) = some(false). ================================================ FILE: src/test/resources/test329_ok.flg ================================================ rel ok ok :- is_sat(`bv_to_int(42) #= int_const(42)`), is_sat(`int_to_bv(int_const(42)) #= 42`), some(M) = get_model([ `int_gt(#x[int], int_const(0))`, `int_lt(#x[int], int_const(2))`, `int_to_bv(#x[int]) #= #y[i32]`], none), query_model(#y[i32], M) = some(1). ================================================ FILE: src/test/resources/test330_ok.flg ================================================ rel ok ok :- X = `bv_extract[32, 16](#x[?], 0, 15)`, Y = `bv_extract[32, 16](#x[?], 16, 31)`, is_valid(`bv_concat(Y, X) #= #x[i32]`). ================================================ FILE: src/test/resources/test331_ok.flg ================================================ rel foo(i32) rel bar(i32 option) rel ok bar(none). foo(X + 1) :- bar(some(X)). bar(some(42)) :- foo(42). ok :- !foo(42). ================================================ FILE: src/test/resources/test332_ok.flg ================================================ @edb rel entity(string) entity("Alice"). entity("Bob"). entity("World"). rel greeting(string) greeting(Y) :- entity(X), some(M) = get_model([`#y[string] #= str_concat("Hello, ", X)`], none), some(Y) = query_model(#y[string], M). rel ok ok :- greeting("Hello, World"), greeting("Hello, Alice"), greeting("Hello, Bob"). ================================================ FILE: src/test/resources/test333_bd.flg ================================================ fun hd(xs: 'a list) : 'a = let x :: _ = xs in x fun foo(xs: 'a list) : bool smt = let x = hd(xs) in `x` ================================================ FILE: src/test/resources/test334_ok.flg ================================================ const x: i32 = 42 const y: i32 = 84 and z: string = "hello" rel ok ok :- x = 42, y = 84, z = "hello". ================================================ FILE: src/test/resources/test335_bd.flg ================================================ const bad(x: i32, y: i32, z: i32): i32 = x + y + z ================================================ FILE: src/test/resources/test336_ok.flg ================================================ rel ok ok :- i32_udiv(-2, 2) = 0x7fffffff, i32_urem(-4, -3) = -4, i32_shl(1, 2) = 4, i32_ashr(-1, 1) = 0xffffffff, i32_lshr(-1, 1) = 0x7fffffff. ================================================ FILE: src/test/resources/test337_ok.flg ================================================ rel ok ok :- i64_udiv(-2L, 2L) = 0x7fffffffffffffffL, i64_urem(-4L, -3L) = -4L, i64_shl(1L, 2L) = 4L, i64_ashr(-1L, 1L) = 0xffffffffffffffffL, i64_lshr(-1L, 1L) = 0x7fffffffffffffffL. ================================================ FILE: src/test/resources/test338_ok.flg ================================================ (* Test case for issue #72 *) rel foo(bool smt) foo(`true`). rel not_ok not_ok :- foo(`#x[i32] #= _`). rel ok ok :- !not_ok. ================================================ FILE: src/test/resources/test339_ok.flg ================================================ (* Test case for issue #74 *) rel bar(i32) rel foo(i32 smt) rel baz foo(`#x[?]`). baz :- bar(X), foo(`X`), X + 1 = 42. bar(0). (* Make sure `foo` is recursive in `ok` stratum, so that when `ok` rule is run, `foo` predicate is reordered to be first *) foo(`0`) :- baz. foo(`#x0[?]`). foo(`#x1[?]`). foo(`#x2[?]`). foo(`#x3[?]`). foo(`#x4[?]`). foo(`#x5[?]`). foo(`#x6[?]`). foo(`#x7[?]`). foo(`#x8[?]`). foo(`#x9[?]`). foo(`#xa[?]`). foo(`#xb[?]`). foo(`#xc[?]`). foo(`#xd[?]`). foo(`#xe[?]`). foo(`#xf[?]`). foo(`41`). bar(41). fun len(xs: 'a list): i32 = match xs with | [] => 0 | _ :: t => 1 + len(t) end rel ok ok :- baz, len(foo(??)) = 19. ================================================ FILE: src/test/resources/test340_ok.flg ================================================ (* Test case for issue #80 *) rel a(i32) rel b(i32) rel ok a(1-1). b(2+2). ok :- a(0), b(4). ================================================ FILE: src/test/resources/test341_bd.flg ================================================ rel ok ok :- `-0x1 #= -1` = _. ================================================ FILE: src/test/resources/test342_bd.flg ================================================ rel ok ok :- `-0x1L #= -1L` = _. ================================================ FILE: src/test/resources/test343_bd.flg ================================================ rel ok ok :- `+0x1 #= 1` = _. ================================================ FILE: src/test/resources/test344_bd.flg ================================================ rel ok ok :- `+0x1L #= 1L` = _. ================================================ FILE: src/test/resources/test345_ok.flg ================================================ rel ok ok :- `-1 #= +1` = _, `-1L #= +1l` = _, `-1F #= +1f` = _, `-1D #= +1d` = _.