diff --git a/key.core.symbolic_execution/src/main/java/de/uka/ilkd/key/symbolic_execution/strategy/SimplifyTermStrategy.java b/key.core.symbolic_execution/src/main/java/de/uka/ilkd/key/symbolic_execution/strategy/SimplifyTermStrategy.java index 5ad174975fd..8f9b35a737b 100644 --- a/key.core.symbolic_execution/src/main/java/de/uka/ilkd/key/symbolic_execution/strategy/SimplifyTermStrategy.java +++ b/key.core.symbolic_execution/src/main/java/de/uka/ilkd/key/symbolic_execution/strategy/SimplifyTermStrategy.java @@ -117,7 +117,7 @@ public Strategy create(Proof proof, StrategyProperties sp) { */ @Override public StrategySettingsDefinition getSettingsDefinition() { - return JavaProfile.DEFAULT.getSettingsDefinition(); + return JavaProfile.getDefault().getSettingsDefinition(); } } } diff --git a/key.core/build.gradle b/key.core/build.gradle index df2ba408900..a6afddbc918 100644 --- a/key.core/build.gradle +++ b/key.core/build.gradle @@ -111,6 +111,7 @@ classes.dependsOn << generateSolverPropsList tasks.withType(Test) { enableAssertions = true + systemProperties(System.properties.findAll { key, value -> key.startsWith("key.") }) } diff --git a/key.core/src/main/antlr4/JmlLexer.g4 b/key.core/src/main/antlr4/JmlLexer.g4 index 8b4eb5880cb..8b2d76b6bad 100644 --- a/key.core/src/main/antlr4/JmlLexer.g4 +++ b/key.core/src/main/antlr4/JmlLexer.g4 @@ -175,6 +175,8 @@ mode expr; /* Java keywords */ BOOLEAN: 'boolean'; BYTE: 'byte'; +CASE: 'case'; +DEFAULT: 'default'; FALSE: 'false'; INSTANCEOF: 'instanceof'; INT: 'int'; @@ -244,6 +246,7 @@ FP_SUBNORMAL: '\\fp_subnormal'; //KeY extension, not official JML FP_ZERO: '\\fp_zero'; //KeY extension, not official JML FREE: '\\free'; //KeY extension, not official JML FRESH: '\\fresh'; +FROM_GOAL: '\\from_goal'; //KeY extension, not official JML INDEX: '\\index'; INDEXOF: '\\seq_indexOf'; //KeY extension, not official JML INTERSECT: '\\intersect'; //KeY extension, not official JML diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index 0060642e03b..cfbaeb4fee8 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -9,9 +9,11 @@ options { tokenVocab=JmlLexer; } @members { private SyntaxErrorReporter errorReporter = new SyntaxErrorReporter(getClass()); public SyntaxErrorReporter getErrorReporter() { return errorReporter;} + private boolean isNextToken(String tokenText) { + return _input.LA(1) != Token.EOF && tokenText.equals(_input.LT(1).getText()); + } } - classlevel_comments: classlevel_comment* EOF; classlevel_comment: classlevel_element | modifiers | set_statement; classlevel_element0: modifiers? (classlevel_element modifiers?); @@ -202,12 +204,35 @@ block_specification: method_specification; block_loop_specification: loop_contract_keyword spec_case ((also_keyword)+ loop_contract_keyword spec_case)*; loop_contract_keyword: LOOP_CONTRACT; -assert_statement: (ASSERT expression | UNREACHABLE) SEMI_TOPLEVEL; +assert_statement: (ASSERT (label=IDENT COLON)? expression | UNREACHABLE) (assertionProof SEMI_TOPLEVEL? | SEMI_TOPLEVEL); //breaks_clause: BREAKS expression; //continues_clause: CONTINUES expression; //returns_clause: RETURNS expression; +// --- proof scripts in JML +assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; +proofCmd: + // TODO allow more than one var in obtain + { isNextToken("obtain") }? obtain=IDENT typespec var=IDENT + ( obtKind=EQUAL_SINGLE expression SEMI + | obtKind=SUCH_THAT expression proofCmdSuffix + | obtKind=FROM_GOAL SEMI + ) + | cmd=IDENT ( proofArg )* proofCmdSuffix + ; + +proofCmdSuffix: + SEMI | BY ( proofCmd | LBRACE (proofCmd+ | proofCmdCase+) RBRACE ) + ; + +proofCmdCase: + CASE ( label=STRING_LITERAL )? COLON ( proofCmd )* + | DEFAULT COLON ( proofCmd )* + ; +proofArg: (argLabel=IDENT COLON)? expression; +// --- + mergeparamsspec: MERGE_PARAMS LBRACE diff --git a/key.core/src/main/antlr4/KeYLexer.g4 b/key.core/src/main/antlr4/KeYLexer.g4 index 544c9371a42..7b632d198cd 100644 --- a/key.core/src/main/antlr4/KeYLexer.g4 +++ b/key.core/src/main/antlr4/KeYLexer.g4 @@ -40,6 +40,7 @@ lexer grammar KeYLexer; } private Token tokenBackStorage = null; + // see: https://keyproject.github.io/key-docs/devel/NewKeyParser/#why-does-the-lexer-required-some-pieces-of-java-code @Override public void emit(Token token) { int MAX_K = 10; @@ -219,6 +220,7 @@ AXIOMS : '\\axioms'; PROBLEM : '\\problem'; CHOOSECONTRACT : '\\chooseContract'; PROOFOBLIGATION : '\\proofObligation'; +// for PROOF see: https://keyproject.github.io/key-docs/devel/NewKeyParser/#why-does-the-lexer-required-some-pieces-of-java-code PROOF : '\\proof'; PROOFSCRIPT : '\\proofScript'; CONTRACTS : '\\contracts'; @@ -389,6 +391,7 @@ LESSEQUAL: '<' '=' | '\u2264'; LGUILLEMETS: '<' '<' | '«' | '‹'; RGUILLEMETS: '>''>' | '»' | '›'; IMPLICIT_IDENT: '<' '$'? (LETTER)+ '>' ('$lmtd')? -> type(IDENT); +MATCH_IDENT: '?' IDENT?; EQV: '<->' | '\u2194'; CHAR_LITERAL diff --git a/key.core/src/main/antlr4/KeYParser.g4 b/key.core/src/main/antlr4/KeYParser.g4 index f111efcedec..1424e262436 100644 --- a/key.core/src/main/antlr4/KeYParser.g4 +++ b/key.core/src/main/antlr4/KeYParser.g4 @@ -7,6 +7,7 @@ parser grammar KeYParser; @members { private SyntaxErrorReporter errorReporter = new SyntaxErrorReporter(getClass()); public SyntaxErrorReporter getErrorReporter() { return errorReporter;} +public boolean allowMatchId = false; // used in proof script parsing } options { tokenVocab=KeYLexer; } // use tokens from STLexer.g4 @@ -147,6 +148,7 @@ string_value: STRING_LITERAL; simple_ident : id=IDENT + | {allowMatchId}? id=MATCH_IDENT ; simple_ident_comma_list @@ -873,7 +875,7 @@ proofScriptExpression: | integer | floatnum | string_literal - | LPAREN (term | seq) RPAREN + | LPAREN {allowMatchId=true;} (term | seq) {allowMatchId=false;} RPAREN | simple_ident | abbreviation | literals diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/JavaInfo.java b/key.core/src/main/java/de/uka/ilkd/key/java/JavaInfo.java index 027dfd622e8..18d274f4129 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/JavaInfo.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/JavaInfo.java @@ -24,6 +24,7 @@ import org.key_project.util.LRUCache; import org.key_project.util.collection.*; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -430,7 +431,7 @@ public static boolean isVisibleTo(SpecificationElement ax, KeYJavaType visibleTo /** * returns a KeYJavaType having the given sort */ - public KeYJavaType getKeYJavaType(Sort sort) { + public @Nullable KeYJavaType getKeYJavaType(Sort sort) { List l = lookupSort2KJTCache(sort); if (l != null && l.size() > 0) { // Return first KeYJavaType found for sort. diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/Recoder2KeYConverter.java b/key.core/src/main/java/de/uka/ilkd/key/java/Recoder2KeYConverter.java index 02cb218f130..b630c85b5bd 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/Recoder2KeYConverter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/Recoder2KeYConverter.java @@ -964,7 +964,9 @@ public CatchAllStatement convert(de.uka.ilkd.key.java.recoderext.CatchAllStateme * @return the converted statement */ public JmlAssert convert(de.uka.ilkd.key.java.recoderext.JmlAssert ja) { - return new JmlAssert(ja.getKind(), ja.getCondition(), positionInfo(ja)); + return new JmlAssert(ja.getKind(), ja.getOptLabel(), ja.getCondition(), + ja.getAssertionProof(), + positionInfo(ja)); } // ------------------- declaration --------------------- diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/SourceElement.java b/key.core/src/main/java/de/uka/ilkd/key/java/SourceElement.java index 479a3069304..37fc0be5a50 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/SourceElement.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/SourceElement.java @@ -96,7 +96,8 @@ public interface SourceElement extends SyntaxElement, EqualsModProperty boolean equalsModProperty(Object o, Property property, V... v) { + default boolean equalsModProperty(Object o, Property property, + V... v) { if (!(o instanceof SourceElement)) { return false; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JMLTransformer.java b/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JMLTransformer.java index 6ea193b97d3..cb70582f565 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JMLTransformer.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JMLTransformer.java @@ -440,7 +440,8 @@ private void transformAssertStatement(TextualJMLAssertStatement stat, de.uka.ilkd.key.java.Position pos = ctx.getStartLocation().getPosition(); final Kind kind = stat.getKind(); - JmlAssert jmlAssert = new JmlAssert(kind, ctx); + JmlAssert jmlAssert = + new JmlAssert(kind, ctx, stat.getAssertionProof(), stat.getOptLabel()); try { updatePositionInformation(jmlAssert, pos); doAttach(jmlAssert, astParent, childIndex); diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JmlAssert.java b/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JmlAssert.java index bfbd83712f1..b4a7c948393 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JmlAssert.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JmlAssert.java @@ -6,6 +6,7 @@ import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; +import org.jspecify.annotations.Nullable; import recoder.java.ProgramElement; import recoder.java.SourceVisitor; import recoder.java.Statement; @@ -23,6 +24,10 @@ public class JmlAssert extends JavaStatement { */ private final TextualJMLAssertStatement.Kind kind; + /** + * The optional proof for an assert statement (not for assume) + */ + private final KeyAst.@Nullable JMLProofScript assertionProof; /** * The condition of this statement in parse tree form @@ -31,12 +36,21 @@ public class JmlAssert extends JavaStatement { private final KeyAst.Expression condition; /** - * @param kind the kind of this statement - * @param condition the condition for this statement + * The optional label for this assertion (may be null) */ - public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition) { + private final @Nullable String optLabel; + + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + String optLabel) { + this(kind, condition, null, optLabel); + } + + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + KeyAst.@Nullable JMLProofScript assertionProof, String optLabel) { this.kind = kind; this.condition = condition; + this.assertionProof = assertionProof; + this.optLabel = optLabel; } /** @@ -48,6 +62,8 @@ public JmlAssert(JmlAssert proto) { super(proto); this.kind = proto.kind; this.condition = proto.condition; + this.assertionProof = proto.assertionProof; + this.optLabel = proto.optLabel; } public TextualJMLAssertStatement.Kind getKind() { @@ -58,6 +74,10 @@ public KeyAst.Expression getCondition() { return condition; } + public KeyAst.@Nullable JMLProofScript getAssertionProof() { + return assertionProof; + } + @Override public int getChildCount() { return 0; @@ -87,4 +107,8 @@ public void accept(SourceVisitor sourceVisitor) { public Statement deepClone() { return new JmlAssert(this); } + + public String getOptLabel() { + return optLabel; + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java b/key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java index 732174aa2fd..4a9bfef5b4e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java @@ -8,10 +8,17 @@ import de.uka.ilkd.key.java.PositionInfo; import de.uka.ilkd.key.java.ProgramElement; import de.uka.ilkd.key.java.visitor.Visitor; +import de.uka.ilkd.key.logic.op.LocationVariable; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; +import de.uka.ilkd.key.speclang.njml.JmlIO; +import de.uka.ilkd.key.speclang.njml.JmlParser; import org.key_project.util.ExtList; +import org.key_project.util.collection.ImmutableList; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A JML assert statement. @@ -29,21 +36,36 @@ public class JmlAssert extends JavaStatement { */ private final TextualJMLAssertStatement.Kind kind; + /* + * Temporary solution until full jml labels are there ... + * (To be clarified if compatible still) + */ + private final String optLabel; + /** * the condition in parse tree form */ - private KeyAst.Expression condition; + private final KeyAst.Expression condition; + + /** + * the assertion proof in parse tree form + */ + private final KeyAst.@Nullable JMLProofScript assertionProof; /** * @param kind assert or assume * @param condition the condition of this statement + * @param assertionProof the optional proof for an assert statement (not for assume) * @param positionInfo the position information for this statement */ - public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + public JmlAssert(TextualJMLAssertStatement.Kind kind, String label, KeyAst.Expression condition, + KeyAst.@Nullable JMLProofScript assertionProof, PositionInfo positionInfo) { super(positionInfo); this.kind = kind; + this.optLabel = label; this.condition = condition; + this.assertionProof = assertionProof; } /** @@ -52,11 +74,15 @@ public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression conditio public JmlAssert(ExtList children) { super(children); this.kind = Objects.requireNonNull(children.get(TextualJMLAssertStatement.Kind.class)); + this.optLabel = children.get(String.class); this.condition = Objects.requireNonNull(children.get(KeyAst.Expression.class)); + // script may be null + this.assertionProof = children.get(KeyAst.JMLProofScript.class); } public JmlAssert(JmlAssert other) { - this(other.kind, other.condition, other.getPositionInfo()); + this(other.kind, other.optLabel, other.condition, other.assertionProof, + other.getPositionInfo()); } public TextualJMLAssertStatement.Kind getKind() { @@ -148,6 +174,10 @@ protected int computeHashCode() { return System.identityHashCode(this); } + public KeyAst.@Nullable JMLProofScript getAssertionProof() { + return assertionProof; + } + @Override public int getChildCount() { return 0; @@ -162,4 +192,30 @@ public ProgramElement getChildAt(int index) { public void visit(Visitor v) { v.performActionOnJmlAssert(this); } + + /** + * This method collects all terms contained in this assertion. This is at least the condition. + * If there is a proof script, all terms in the proof script are collected as well. + * + * @return a freshly created list of at least one term + */ + public @NonNull ImmutableList collectTerms() { + ImmutableList result = ImmutableList.of(); + if (assertionProof != null) { + result = result.prepend(assertionProof.collectTerms()); + } + result = result.prepend(condition.ctx); + return result; + } + + public ImmutableList collectVariablesInProof(JmlIO io) { + if (assertionProof != null) { + return assertionProof.getObtainedProgramVars(io); + } + return ImmutableList.of(); + } + + public String getOptLabel() { + return optLabel; + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java index 7a0e26f1fca..a37b126bf4e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java @@ -1513,6 +1513,8 @@ public void performActionOnJmlAssert(JmlAssert x) { ProgramElement createNewElement(ExtList changeList) { changeList.add(x.getKind()); changeList.add(x.getCondition()); + changeList.add(x.getAssertionProof()); + changeList.add(x.getOptLabel()); return new JmlAssert(changeList); } }; diff --git a/key.core/src/main/java/de/uka/ilkd/key/logic/ProgramElementName.java b/key.core/src/main/java/de/uka/ilkd/key/logic/ProgramElementName.java index 2c9251559de..a0955584e47 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/logic/ProgramElementName.java +++ b/key.core/src/main/java/de/uka/ilkd/key/logic/ProgramElementName.java @@ -70,7 +70,8 @@ public ProgramElementName(String name, NameCreationInfo creationInfo, Comment[] public ProgramElementName(String n, String q) { super(q + "::" + n); - assert !q.isEmpty() : "Tried to create qualified name with missing qualifier"; + //weigl: This does make sense, as ProgramElementNames can be in the default (empty package). + //assert !q.isEmpty() : "Tried to create qualified name with missing qualifier"; this.qualifierString = q.intern(); this.shortName = n.intern(); diff --git a/key.core/src/main/java/de/uka/ilkd/key/logic/TermImpl.java b/key.core/src/main/java/de/uka/ilkd/key/logic/TermImpl.java index cd06367349d..32429104bdb 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/logic/TermImpl.java +++ b/key.core/src/main/java/de/uka/ilkd/key/logic/TermImpl.java @@ -349,7 +349,7 @@ protected int computeHashCode() { } @Override - public boolean equalsModProperty(Object o, Property property, V... v) { + public boolean equalsModProperty(Object o, Property property, V... v) { if (!(o instanceof JTerm other)) { return false; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/logic/equality/EqualsModProperty.java b/key.core/src/main/java/de/uka/ilkd/key/logic/equality/EqualsModProperty.java index bf5228374b8..2bda6af7c12 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/logic/equality/EqualsModProperty.java +++ b/key.core/src/main/java/de/uka/ilkd/key/logic/equality/EqualsModProperty.java @@ -49,7 +49,7 @@ public interface EqualsModProperty { * @param the type of the additional parameters needed by {@code property} for the * comparison */ - boolean equalsModProperty(Object o, Property property, V... v); + boolean equalsModProperty(Object o, Property property, V... v); /** * Computes the hash code according to the given ignored {@code property}. diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java new file mode 100644 index 00000000000..8404754fda9 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java @@ -0,0 +1,398 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.macros; + +import java.util.*; +import java.util.ArrayList; +import java.util.stream.Collectors; + +import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.control.UserInterfaceControl; +import de.uka.ilkd.key.java.JavaTools; +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.java.SourceElement; +import de.uka.ilkd.key.java.statement.JmlAssert; +import de.uka.ilkd.key.java.statement.MethodFrame; +import de.uka.ilkd.key.logic.DefaultVisitor; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.JavaBlock; +import de.uka.ilkd.key.logic.op.*; +import de.uka.ilkd.key.nparser.KeyAst; +import de.uka.ilkd.key.parser.Location; +import de.uka.ilkd.key.proof.*; +import de.uka.ilkd.key.proof.mgt.SpecificationRepository; +import de.uka.ilkd.key.prover.impl.DefaultTaskStartedInfo; +import de.uka.ilkd.key.rule.JmlAssertBuiltInRuleApp; +import de.uka.ilkd.key.scripts.ProofScriptEngine; +import de.uka.ilkd.key.scripts.ScriptCommandAst; +import de.uka.ilkd.key.scripts.ScriptException; +import de.uka.ilkd.key.scripts.TermWithHoles; +import de.uka.ilkd.key.speclang.njml.JmlLexer; +import de.uka.ilkd.key.speclang.njml.JmlParser; +import de.uka.ilkd.key.speclang.njml.JmlParser.ProofArgContext; +import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdCaseContext; +import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdContext; +import de.uka.ilkd.key.util.MiscTools; + +import org.key_project.logic.Term; +import org.key_project.logic.op.Modality; +import org.key_project.prover.engine.ProverTaskListener; +import org.key_project.prover.engine.TaskStartedInfo; +import org.key_project.prover.rules.RuleApp; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.java.StringUtil; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApplyScriptsMacro extends AbstractProofMacro { + + private static final Logger LOGGER = LoggerFactory.getLogger(ApplyScriptsMacro.class); + + private final @Nullable ProofMacro fallBackMacro; + + public ApplyScriptsMacro(ProofMacro fallBackMacro) { + this.fallBackMacro = fallBackMacro; + } + + @Override + public String getName() { + return "Apply scripts macro"; + } + + @Override + public String getCategory() { + return null; + } + + @Override + public String getDescription() { + return "Apply scripts"; + } + + @Override + public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, + PosInOccurrence posInOcc) { + return fallBackMacro != null && fallBackMacro.canApplyTo(proof, goals, posInOcc) + || goals.exists(g -> getJmlAssert(g.node()) != null); + } + + record ObtainAwareTerm(JTerm term) { + JTerm resolve(Map obtainMap, Services services) { + OpReplacer pvr = new OpReplacer(obtainMap, services.getTermFactory()); + JTerm result = pvr.replace(term); + assertNoObtainVarsLeft(result, obtainMap); + return result; + } + + private void assertNoObtainVarsLeft(JTerm term, + Map obtainMap) { + var v = new DefaultVisitor() { + @Override + public void visit(Term visited) { + if (obtainMap.containsKey(term.op())) { + throw new RuntimeException( + "Use of obtain variable before it being obtained: " + term.op()); + } + } + }; + term.execPreOrder(v); + } + } + + private static JmlAssert getJmlAssert(Node node) { + RuleApp ruleApp = node.parent().getAppliedRuleApp(); + if (ruleApp instanceof JmlAssertBuiltInRuleApp) { + JTerm target = (JTerm) ruleApp.posInOccurrence().subTerm(); + if (target.op() instanceof UpdateApplication) { + target = UpdateApplication.getTarget(target); + } + final SourceElement activeStatement = JavaTools.getActiveStatement(target.javaBlock()); + if (activeStatement instanceof JmlAssert jmlAssert + && jmlAssert.getAssertionProof() != null) { + return jmlAssert; + } + } + return null; + } + + private static @Nullable OpReplacer getUpdateReplacer(Goal goal) { + RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); + Term appliedOn = ruleApp.posInOccurrence().subTerm(); + if (appliedOn.op() instanceof UpdateApplication) { + var update = UpdateApplication.getUpdate((JTerm) appliedOn); + Map updates = new HashMap<>(); + Services services = goal.proof().getServices(); + collectUpdates(update, updates, services); + return new OpReplacer(updates, services.getTermFactory()); + } + return null; + } + + private static void collectUpdates(JTerm update, Map updates, Services services) { + switch (update.op()) { + case ElementaryUpdate eu -> + updates.put(services.getTermBuilder().var((ProgramVariable) eu.lhs()), + update.sub(0)); + + case UpdateJunctor uj -> { + collectUpdates(update.sub(0), updates, services); + collectUpdates(update.sub(1), updates, services); + } + + default -> + throw new IllegalStateException( + "Unexpected update operation: " + update.op().getClass()); + } + } + + private static JavaBlock getJavaBlock(Goal goal) { + RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); + JTerm appliedOn = (JTerm) ruleApp.posInOccurrence().subTerm(); + if (appliedOn.op() instanceof UpdateApplication) { + appliedOn = UpdateApplication.getTarget(appliedOn); + } + assert appliedOn.op() instanceof Modality; + return appliedOn.javaBlock(); + } + + @Override + public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, + ImmutableList goals, PosInOccurrence posInOcc, ProverTaskListener listener) + throws Exception { + ArrayList laterGoals = new ArrayList<>(goals.size()); + for (Goal goal : goals) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + JmlAssert jmlAssert = getJmlAssert(goal.node()); + if (jmlAssert == null) { + laterGoals.add(goal); + continue; + } + + listener.taskStarted(new DefaultTaskStartedInfo(TaskStartedInfo.TaskKind.Other, + "Running attached script from goal " + goal.node().serialNr(), 0)); + + KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); + Map termMap = + getTermMap(jmlAssert, getJavaBlock(goal), proof.getServices()); + // We heavily rely on that variables have been computed before, otherwise this will + // raise an NPE. + Map obtainMap = + makeObtainVarMap(jmlAssert.collectVariablesInProof(null)); + @Nullable + OpReplacer updateReplacer = getUpdateReplacer(goal); + List renderedProof = + renderProof(proofScript, termMap, updateReplacer, proof.getServices()); + ProofScriptEngine pse = new ProofScriptEngine(proof); + pse.setInitiallySelectedGoal(goal); + pse.getStateMap().putUserData("jml.obtainVarMap", obtainMap); + pse.getStateMap().getValueInjector().addConverter(JTerm.class, ObtainAwareTerm.class, + oat -> oat.resolve(obtainMap, goal.proof().getServices())); + // TODO: Perhaps have holes also in JML? + pse.getStateMap().getValueInjector().addConverter(TermWithHoles.class, + ObtainAwareTerm.class, + oat -> new TermWithHoles(oat.resolve(obtainMap, goal.proof().getServices()))); + pse.getStateMap().getValueInjector().addConverter(boolean.class, ObtainAwareTerm.class, + oat -> Boolean.parseBoolean(oat.term.toString())); + LOGGER.debug("---- Script"); + LOGGER.debug(renderedProof.stream().map(ScriptCommandAst::asCommandLine) + .collect(Collectors.joining("\n"))); + LOGGER.debug("---- End Script"); + + pse.execute((AbstractUserInterfaceControl) uic, renderedProof); + } + listener.taskStarted(new DefaultTaskStartedInfo(TaskStartedInfo.TaskKind.Other, + "Running fallback macro on the remaining goals", 0)); + for (Goal goal : laterGoals) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + if (fallBackMacro != null) { + fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + } + + } + + return new ProofMacroFinishedInfo(this, proof); + } + + + private Map getTermMap(JmlAssert jmlAssert, JavaBlock javaBlock, + Services services) { + SpecificationRepository.@Nullable JmlStatementSpec jmlspec = + services.getSpecificationRepository().getStatementSpec(jmlAssert); + if (jmlspec == null) { + throw new IllegalStateException( + "No specification found for JML assert statement at " + jmlAssert); + } + ImmutableList terms = ImmutableList.of(); + for (int i = jmlspec.terms().size() - 1; i >= 1; i--) { + terms = terms.prepend(correctSelfVar(i, javaBlock, jmlspec, services)); + } + ImmutableList jmlExprs = jmlAssert.collectTerms().tail(); + Map result = new IdentityHashMap<>(); + assert terms.size() == jmlExprs.size(); + for (int i = 0; i < terms.size(); i++) { + result.put(jmlExprs.get(i), terms.get(i)); + } + return result; + } + + /** + * For some reason, the self variable in the spec is not the same as the self variable and needs + * to + * be corrected. + */ + private JTerm correctSelfVar(int index, JavaBlock javaBlock, + SpecificationRepository.JmlStatementSpec spec, Services services) { + final MethodFrame frame = JavaTools.getInnermostMethodFrame(javaBlock, services); + final JTerm self = MiscTools.getSelfTerm(frame, services); + return spec.getTerm(services, self, index); + + } + + private Map makeObtainVarMap( + ImmutableList locationVariables) { + HashMap result = new HashMap<>(); + for (LocationVariable lv : locationVariables) { + result.put(lv, null); + } + return result; + } + + private static List renderProof(KeyAst.JMLProofScript script, + Map termMap, @Nullable OpReplacer update, Services services) + throws ScriptException { + List result = new ArrayList<>(); + // Push current settings onto the settings stack + result.add(new ScriptCommandAst("set", Map.of("stack", "push"), List.of())); + // Prepare by resolving the update + result.add(new ScriptCommandAst("oss", Map.of("recentOnly", true), List.of())); + for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); + } + // Pop settings stack to restore old settings + result.add(new ScriptCommandAst("set", Map.of("stack", "pop"), List.of())); + return result; + } + + private static List renderProofCmd(ProofCmdContext ctx, + Map termMap, + @Nullable OpReplacer update, Services services) throws ScriptException { + List result = new ArrayList<>(); + + // Push the current branch context + result.add(new ScriptCommandAst("branches", Map.of(), List.of("push"))); + + // Compose the command itself + if (ctx.obtain != null) { + ScriptCommandAst command = renderObtainCommand(ctx, termMap, update, services); + result.add(command); + } else { + ScriptCommandAst command = renderRegularCommand(ctx, termMap, update, services); + result.add(command); + } + + // handle followup proofCmd if present + JmlParser.ProofCmdSuffixContext suffix = ctx.proofCmdSuffix(); + if (suffix != null) { + if (!suffix.proofCmd().isEmpty()) { + result.add(new ScriptCommandAst("branches", Map.of(), List.of("single"))); + for (ProofCmdContext proofCmdContext : suffix.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); + } + } + + // handle proofCmdCases if present + for (ProofCmdCaseContext pcase : suffix.proofCmdCase()) { + String label = StringUtil.stripQuotes(pcase.label.getText()); + result.add(new ScriptCommandAst("branches", Map.of("branch", label), + List.of("select"))); + for (ProofCmdContext proofCmdContext : pcase.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); + } + } + } + + // Pop the branch stack + result.add(new ScriptCommandAst("branches", Map.of(), List.of("pop"))); + + return result; + } + + private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, + Map termMap, + @Nullable OpReplacer update, Services services) throws ScriptException { + Map named = new HashMap<>(); + + String argName = switch (ctx.obtKind.getType()) { + case JmlLexer.SUCH_THAT -> "such_that"; + case JmlLexer.EQUAL_SINGLE -> "equals"; + case JmlLexer.FROM_GOAL -> "from_goal"; + default -> throw new ScriptException("Unknown obtain kind: " + ctx.obtKind.getText()); + }; + + named.put("var", ctx.var.getText()); + + if (ctx.expression() == null) { + named.put(argName, true); + } else { + JmlParser.ExpressionContext exp = ctx.expression(); + Object value; + if (isStringLiteral(exp)) { + value = StringUtil.stripQuotes(exp.getText()); + } else { + value = termMap.get(exp); + if (update != null) { + // Wrap in update application if an update is present + value = update.replace((JTerm) value); + } + } + named.put(argName, value); + } + + return new ScriptCommandAst("__obtain", named, List.of(), Location.fromToken(ctx.start)); + } + + private static @NonNull ScriptCommandAst renderRegularCommand(ProofCmdContext ctx, + Map termMap, @Nullable OpReplacer update, Services services) { + Map named = new HashMap<>(); + List positional = new ArrayList<>(); + for (ProofArgContext argContext : ctx.proofArg()) { + Object value; + JmlParser.ExpressionContext exp = argContext.expression(); + if (isStringLiteral(exp)) { + value = StringUtil.stripQuotes(exp.getText()); + } else { + value = termMap.get(exp); + if (update != null) { + // Wrap in update application if an update is present + value = update.replace((JTerm) value); + } + } + if (value instanceof JTerm term) { + value = new ObtainAwareTerm(term); + } + if (argContext.argLabel != null) { + named.put(argContext.argLabel.getText(), value); + } else { + positional.add(value); + } + } + return new ScriptCommandAst(ctx.cmd.getText(), named, positional, + Location.fromToken(ctx.start)); + } + + + private static boolean isStringLiteral(JmlParser.ExpressionContext ctx) { + return ctx.start == ctx.stop && ctx.start.getType() == JmlParser.STRING_LITERAL; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareAutoMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareAutoMacro.java new file mode 100644 index 00000000000..5a9dca3ea20 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareAutoMacro.java @@ -0,0 +1,112 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +//// This file is part of KeY - Integrated Deductive Software Design +//// +//// Copyright (C) 2001-2011 Universitaet Karlsruhe (TH), Germany +//// Universitaet Koblenz-Landau, Germany +//// Chalmers University of Technology, Sweden +//// Copyright (C) 2011-2014 Karlsruhe Institute of Technology, Germany +//// Technical University Darmstadt, Germany +//// Chalmers University of Technology, Sweden +//// +//// The KeY system is protected by the GNU General +//// Public License. See LICENSE.TXT for details. +//// +// +// package de.uka.ilkd.key.macros; +// +// import de.uka.ilkd.key.java.ProofCommandStatement; +// import de.uka.ilkd.key.logic.Name; +// import de.uka.ilkd.key.logic.PosInOccurrence; +// import de.uka.ilkd.key.proof.Goal; +// import de.uka.ilkd.key.proof.Proof; +// import de.uka.ilkd.key.rule.ProofCommandStatementRule; +// import de.uka.ilkd.key.rule.RuleApp; +// import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdContext; +// import de.uka.ilkd.key.strategy.RuleAppCost; +// import de.uka.ilkd.key.strategy.RuleAppCostCollector; +// import de.uka.ilkd.key.strategy.Strategy; +// +// import java.util.IdentityHashMap; +// import java.util.Map; +// +// public class ScriptAwareAutoMacro extends StrategyProofMacro { +// +// public ScriptAwareAutoMacro() { super(); } +// +// private Map detectedProofs = new IdentityHashMap<>(); +// +// @Override +// public String getName() { +// return "Script-aware auto mode"; +// } +// +// @Override +// public String getCategory() { +// return "Auto Pilot"; +// } +// +// @Override +// public String getDescription() { +// return "TODO"; +// } +// +// public Map getDetectedProofs() { +// return detectedProofs; +// } +// +// @Override +// public String getScriptCommandName() { +// return "script-auto"; +// } +// +// private static final Name NAME = new Name("Script-aware filter strategy"); +// +// private class ScriptAwareStrategy implements Strategy { +// +// private final Strategy delegate; +// +// public ScriptAwareStrategy(Proof proof, PosInOccurrence posInOcc) { +// this.delegate = proof.getActiveStrategy(); +// } +// +// @Override +// public Name name() { +// return NAME; +// } +// +// @Override +// public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { +// if (detectedProofs.containsKey(goal)) { +// // we had found a command earlier. +// return false; +// } +// if (app.rule() instanceof ProofCommandStatementRule) { +// detectedProofs.put(goal, ProofCommandStatementRule.getCommand(pio)); +// } +// return delegate.isApprovedApp(app, pio, goal); +// } +// +// @Override +// public void instantiateApp(RuleApp app, PosInOccurrence pio, Goal goal, +// RuleAppCostCollector collector) { +// delegate.instantiateApp(app, pio, goal, collector); +// } +// +// @Override +// public boolean isStopAtFirstNonCloseableGoal() { +// return false; +// } +// +// @Override +// public RuleAppCost computeCost(RuleApp app, PosInOccurrence pos, Goal goal) { +// return delegate.computeCost(app, pos, goal); +// } +// } +// +// @Override +// protected Strategy createStrategy(Proof proof, PosInOccurrence posInOcc) { +// return new ScriptAwareStrategy(proof, posInOcc); +// } +// } diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java new file mode 100644 index 00000000000..397118f2ca8 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java @@ -0,0 +1,64 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +// This file is part of KeY - Integrated Deductive Software Design +// +// Copyright (C) 2001-2011 Universitaet Karlsruhe (TH), Germany +// Universitaet Koblenz-Landau, Germany +// Chalmers University of Technology, Sweden +// Copyright (C) 2011-2014 Karlsruhe Institute of Technology, Germany +// Technical University Darmstadt, Germany +// Chalmers University of Technology, Sweden +// +// The KeY system is protected by the GNU General +// Public License. See LICENSE.TXT for details. +// + +package de.uka.ilkd.key.macros; + +/** + * This class captures a proof macro which is meant to fully automise KeY proof + * workflow if scripts are present in the JML code. + * + * It is experimental. + * + * It performs the following steps: + *
    + *
  1. Finish symbolic execution + *
  2. Apply macros + *
  3. Try to close provable goals + *
+ * + * @author mattias ulbrich + * @see ScriptAwarePrepMacro + */ +public class ScriptAwareMacro extends SequentialProofMacro { + + private final ProofMacro autoMacro = new SymbolicExecutionOnlyMacro(); // FinishSymbolicExecutionMacro(); + private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(new TryCloseMacro()); + + @Override + public String getScriptCommandName() { + return "script-auto"; + } + + @Override + public String getName() { + return "Script-aware Auto"; + } + + @Override + public String getCategory() { + return "Auto Pilot"; + } + + @Override + public String getDescription() { + return "TODO"; + } + + @Override + protected ProofMacro[] createProofMacroArray() { + return new ProofMacro[] { autoMacro, applyMacro }; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwarePrepMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwarePrepMacro.java new file mode 100644 index 00000000000..50c7dcb33aa --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwarePrepMacro.java @@ -0,0 +1,65 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +// This file is part of KeY - Integrated Deductive Software Design +// +// Copyright (C) 2001-2011 Universitaet Karlsruhe (TH), Germany +// Universitaet Koblenz-Landau, Germany +// Chalmers University of Technology, Sweden +// Copyright (C) 2011-2014 Karlsruhe Institute of Technology, Germany +// Technical University Darmstadt, Germany +// Chalmers University of Technology, Sweden +// +// The KeY system is protected by the GNU General +// Public License. See LICENSE.TXT for details. +// + +package de.uka.ilkd.key.macros; + +/** + * This class captures a proof macro which is meant to automise KeY proof + * workflow if scripts are present in the JML code. + * + * It is experimental. + * + * It performs the following steps: + *
    + *
  1. Finish symbolic execution + *
  2. Apply macros + *
  3. It does not try to close provable goals + *
+ * + * @author mattias ulbrich + * @see ScriptAwareMacro + */ +public class ScriptAwarePrepMacro extends SequentialProofMacro { + + private final ProofMacro autoMacro = new SymbolicExecutionOnlyMacro(); // new + // FinishSymbolicExecutionMacro(); + private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(null); + + @Override + public String getScriptCommandName() { + return "script-prep-auto"; + } + + @Override + public String getName() { + return "Script-aware Prep Auto"; + } + + @Override + public String getCategory() { + return "Auto Pilot"; + } + + @Override + public String getDescription() { + return "TODO"; + } + + @Override + protected ProofMacro[] createProofMacroArray() { + return new ProofMacro[] { autoMacro, applyMacro }; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.java new file mode 100644 index 00000000000..4c3a6948a25 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.java @@ -0,0 +1,197 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.macros; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import de.uka.ilkd.key.logic.op.Junctor; +import de.uka.ilkd.key.logic.op.UpdateApplication; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.rule.*; +import de.uka.ilkd.key.strategy.Strategy; + +import org.key_project.logic.Name; +import org.key_project.logic.Term; +import org.key_project.logic.op.Modality; +import org.key_project.prover.rules.Rule; +import org.key_project.prover.rules.RuleApp; +import org.key_project.prover.rules.RuleSet; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.Sequent; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.Streams; + +import org.jspecify.annotations.NonNull; + +/** + * This macro is very restritive in which rules are allowed to be applied for symbolic + * execution. No reasoning rules should ever be applied. + * + * @author mattias ulbrich + */ +public class SymbolicExecutionOnlyMacro extends StrategyProofMacro { + + private static final List ADMITTED_RULES; + private static final List ADMITTED_RULE_SETS; + static { + try { + ADMITTED_RULES = Arrays.asList( + Streams.toString(SymbolicExecutionOnlyMacro.class + .getResourceAsStream("SymbolicExecutionOnlyMacro.admittedRules.txt")) + .split("\n")); + ADMITTED_RULE_SETS = Arrays.asList( + Streams.toString(SymbolicExecutionOnlyMacro.class + .getResourceAsStream("SymbolicExecutionOnlyMacro.admittedRuleSets.txt")) + .split("\n")); + } catch (IOException e) { + throw new RuntimeException( + "Failed to load admitted rules for symbolic execution macro.", e); + } + } + + @Override + public String getName() { + return "Symbolic Execution Only"; + } + + @Override + public String getCategory() { + return "Auto Pilot"; + } + + @Override + public String getScriptCommandName() { + return "symbex-only"; + } + + @Override + public String getDescription() { + return "Continue symbolic execution until no more modality is on the sequent."; + } + + @Override + protected Strategy<@NonNull Goal> createStrategy(Proof proof, + PosInOccurrence posInOcc) { + return new FilterSymbexStrategy(proof.getActiveStrategy()); + } + + public static boolean isAdmittedRule(RuleApp ruleApp) { + Rule rule = ruleApp.rule(); + String name = rule.name().toString(); + if (ADMITTED_RULES.contains(name)) { + return true; + } + + if (rule instanceof org.key_project.prover.rules.Taclet taclet) { + for (RuleSet rs : taclet.getRuleSets()) { + if (ADMITTED_RULE_SETS.contains(rs.name().toString())) { + return true; + } + } + } + + if ("ifthenelse_split_for".equals(name)) { + Term iteTerm = ruleApp.posInOccurrence().subTerm(); + Term then = iteTerm.sub(1); + Term elze = iteTerm.sub(2); + if (isUpdatedModality(then) && isUpdatedModality(elze)) { + return true; + } + } + + // apply OSS to () calls. + if (rule instanceof OneStepSimplifier) { + PosInOccurrence pio = ruleApp.posInOccurrence(); + var target = pio.subTerm(); + if (isUpdatedModality(target)) { + return true; + } + } + + if (rule instanceof UseOperationContractRule || + rule instanceof JmlAssertRule || + rule instanceof WhileInvariantRule || + rule instanceof LoopScopeInvariantRule) + return true; + + return false; + } + + /** + * return true if there is a boolean combination of updated modalities + */ + private static boolean isUpdatedModality(Term term) { + while (term.op() instanceof UpdateApplication) { + term = term.sub(1); + } + if (term.op() instanceof Modality) { + ; + return true; + } + if (term.op() == Junctor.IMP || term.op() == Junctor.AND) { + return term.subs().stream().allMatch(SymbolicExecutionOnlyMacro::isUpdatedModality); + } + return false; + } + + private static class FilterSymbexStrategy extends FilterStrategy { + + private static final Name NAME = new Name(FilterSymbexStrategy.class.getSimpleName()); + private final Map modalityCache = new WeakHashMap<>(); + + public FilterSymbexStrategy(Strategy<@NonNull Goal> delegate) { + super(delegate); + } + + @Override + public Name name() { + return NAME; + } + + @Override + public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { + if (!hasModality(goal) || isThrowNullBranch(goal)) { + return false; + } + return isAdmittedRule(app) && super.isApprovedApp(app, pio, goal); + } + + /// Special case: Avoid infinite recursion on the "throw null" branch of tryCatchThrow + /// by forbidding continuation on this branch. + private boolean isThrowNullBranch(Goal goal) { + return "Null reference in throw".equals(goal.node().getNodeInfo().getBranchLabel()); + } + + private boolean hasModality(Goal goal) { + return modalityCache.computeIfAbsent(goal.node().sequent(), this::hasModality); + } + + private boolean hasModality(Sequent seq) { + for (SequentFormula formula : seq.asList()) { + if (hasModality(formula.formula())) { + return true; + } + } + return false; + } + + private boolean hasModality(Term term) { + if (term.op() instanceof Modality) { + return true; + } + return term.subs().stream().anyMatch(this::hasModality); + } + + @Override + public boolean isStopAtFirstNonCloseableGoal() { + return false; + } + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/macros/TryCloseSideBranchesMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/TryCloseSideBranchesMacro.java new file mode 100644 index 00000000000..5b2a2e5abb2 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/TryCloseSideBranchesMacro.java @@ -0,0 +1,77 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.macros; + + +import de.uka.ilkd.key.control.UserInterfaceControl; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.Node; +import de.uka.ilkd.key.proof.Proof; + +import org.key_project.prover.engine.ProverTaskListener; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.util.collection.ImmutableList; + +public class TryCloseSideBranchesMacro extends TryCloseMacro { + + /** + * Instantiates a new try close macro. + * No changes to the max number of steps. + */ + public TryCloseSideBranchesMacro() { + super(-1); + } + + /** + * Instantiates a new try close macro. + * + * @param numberSteps + * the max number of steps. -1 means no change. + */ + public TryCloseSideBranchesMacro(int numberSteps) { + super(numberSteps); + } + + @Override + public String getName() { + return "Close Provable Goals Below (Only side branches)"; + } + + @Override + public String getScriptCommandName() { + return "tryclose-sidebranches"; + } + + @Override + public String getDescription() { + return "Closes closable goals, leave rest untouched (see settings AutoPrune). " + + "Applies only to supposedly easy side goals (null reference, index out of bounds) " + + "beneath the selected node."; + } + + @Override + public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, + Proof proof, + ImmutableList goals, + PosInOccurrence posInOcc, + ProverTaskListener listener) throws InterruptedException { + ImmutableList sideGoals = goals.filter(TryCloseSideBranchesMacro::isSideGoal); + + return super.applyTo(uic, proof, sideGoals, posInOcc, listener); + } + + private static boolean isSideGoal(Goal g) { + Node node = g.node(); + while (node != null && node.getNodeInfo() != null + && node.getNodeInfo().getBranchLabel() != null) { + String label = node.getNodeInfo().getBranchLabel(); + if (label.contains("Null Reference") + || label.contains("Index Out of Bounds")) { + return true; + } + node = node.parent(); + } + return false; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java b/key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java index e3e50418712..e37bdd96baa 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java +++ b/key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java @@ -11,6 +11,9 @@ import java.util.List; import de.uka.ilkd.key.java.Position; +import de.uka.ilkd.key.java.abstraction.KeYJavaType; +import de.uka.ilkd.key.logic.ProgramElementName; +import de.uka.ilkd.key.logic.op.LocationVariable; import de.uka.ilkd.key.nparser.builder.BuilderHelpers; import de.uka.ilkd.key.nparser.builder.ChoiceFinder; import de.uka.ilkd.key.nparser.builder.FindProblemInformation; @@ -21,8 +24,11 @@ import de.uka.ilkd.key.scripts.ScriptCommandAst; import de.uka.ilkd.key.settings.Configuration; import de.uka.ilkd.key.settings.ProofSettings; +import de.uka.ilkd.key.speclang.njml.JmlIO; import de.uka.ilkd.key.speclang.njml.JmlParser; +import de.uka.ilkd.key.speclang.njml.JmlParserBaseVisitor; +import org.key_project.util.collection.ImmutableList; import org.key_project.util.java.StringUtil; import org.antlr.v4.runtime.CharStream; @@ -47,7 +53,7 @@ */ public abstract class KeyAst { - final @NonNull T ctx; + public final @NonNull T ctx; protected KeyAst(@NonNull T ctx) { this.ctx = ctx; @@ -223,6 +229,72 @@ public Expression(JmlParser.@NonNull ExpressionContext ctx) { } } + public static class JMLProofScript extends KeyAst { + + private static class ObtainedVarsVisitor extends JmlParserBaseVisitor { + private ImmutableList collectedVars = ImmutableList.of(); + private final JmlIO io; + + private ObtainedVarsVisitor(JmlIO io) { + this.io = io; + } + + @Override + public Void visitProofCmd(JmlParser.ProofCmdContext ctx) { + if (ctx.obtain != null) { + KeYJavaType type = io.translateType(ctx.typespec()); + ProgramElementName name = new ProgramElementName(ctx.var.getText()); + collectedVars = collectedVars.prepend(new LocationVariable(name, type, true)); + } + return null; + } + } + + private static class TermCollectionVisitor extends JmlParserBaseVisitor { + private ImmutableList collectedTerms = ImmutableList.of(); + + @Override + public Void visitExpression(JmlParser.ExpressionContext ctx) { + collectedTerms = collectedTerms.prepend(ctx); + return null; + } + } + + private ImmutableList obtainedProgramVars; + + public JMLProofScript(JmlParser.@NonNull AssertionProofContext ctx) { + super(ctx); + } + + public static JMLProofScript fromContext(JmlParser.AssertionProofContext ctx) { + if (ctx == null) { + return null; + } else { + return new JMLProofScript(ctx); + } + } + + public ImmutableList getObtainedProgramVars(JmlIO io) { + if (obtainedProgramVars == null) { + var visitor = new ObtainedVarsVisitor(io); + ctx.accept(visitor); + obtainedProgramVars = visitor.collectedVars; + } + return obtainedProgramVars; + } + + /** + * returns a list of all term parse trees in this proof script. + * + * Todo: Consider caching the result if this is called very often. + */ + public @NonNull ImmutableList collectTerms() { + TermCollectionVisitor visitor = new TermCollectionVisitor(); + ctx.accept(visitor); + return visitor.collectedTerms.reverse(); + } + } + public static class Term extends KeyAst { Term(KeYParser.TermContext ctx) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/nparser/ParsingFacade.java b/key.core/src/main/java/de/uka/ilkd/key/nparser/ParsingFacade.java index 70881d2dfc6..2cbd1d5340b 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/nparser/ParsingFacade.java +++ b/key.core/src/main/java/de/uka/ilkd/key/nparser/ParsingFacade.java @@ -100,7 +100,7 @@ private static KeYParser createParser(TokenSource lexer) { } - private static KeYParser createParser(CharStream stream) { + public static KeYParser createParser(CharStream stream) { return createParser(createLexer(stream)); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/nparser/builder/DefaultBuilder.java b/key.core/src/main/java/de/uka/ilkd/key/nparser/builder/DefaultBuilder.java index 059f24f1696..65b6291f0f4 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/nparser/builder/DefaultBuilder.java +++ b/key.core/src/main/java/de/uka/ilkd/key/nparser/builder/DefaultBuilder.java @@ -300,7 +300,7 @@ public Object visitSimple_ident_dots_comma_list( @Override public String visitSimple_ident(KeYParser.Simple_identContext ctx) { - return ctx.IDENT().getText(); + return ctx.id.getText(); } @Override diff --git a/key.core/src/main/java/de/uka/ilkd/key/parser/Location.java b/key.core/src/main/java/de/uka/ilkd/key/parser/Location.java index 4635d37f311..5f630035269 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/parser/Location.java +++ b/key.core/src/main/java/de/uka/ilkd/key/parser/Location.java @@ -11,6 +11,7 @@ import java.util.Optional; import de.uka.ilkd.key.java.Position; +import de.uka.ilkd.key.java.PositionInfo; import de.uka.ilkd.key.util.MiscTools; import org.antlr.v4.runtime.IntStream; @@ -68,6 +69,16 @@ public static Location fromToken(Token token) { public Position getPosition() { return position; } + public static Location fromPositionInfo(PositionInfo info) { + Optional uri = info.getURI(); + if (uri.isEmpty()) { + return UNDEFINED; + } else { + Position pos = info.getStartPosition(); + return new Location(uri.get(), pos); + } + } + /** * Internal string representation. Do not rely on format! */ diff --git a/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java b/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java index df7141b3eea..ca3fc3619d9 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java @@ -1955,6 +1955,12 @@ public void performActionOnJmlAssert(JmlAssert jmlAssert) { layouter.print(text); } } + + if (jmlAssert.getAssertionProof() != null) { + // For now: Just say that there is a script. It can be seen in the source pane anyways. + layouter.print(" \\by ..."); + } + layouter.end(); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/proof/NodeInfo.java b/key.core/src/main/java/de/uka/ilkd/key/proof/NodeInfo.java index c6f08143795..cd2043f8179 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/proof/NodeInfo.java +++ b/key.core/src/main/java/de/uka/ilkd/key/proof/NodeInfo.java @@ -33,6 +33,7 @@ import org.key_project.prover.sequent.SequentChangeInfo; import org.key_project.util.collection.ImmutableList; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +50,7 @@ public class NodeInfo { /** firstStatement stripped of method frames */ private SourceElement activeStatement = null; - private String branchLabel = null; + private @Nullable String branchLabel = null; /** flag true if the first and active statement have been determined */ private boolean determinedFstAndActiveStatement = false; @@ -265,7 +266,7 @@ public SourceElement getActiveStatement() { * * @return branch label */ - public String getBranchLabel() { + public @Nullable String getBranchLabel() { return branchLabel; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/proof/Proof.java b/key.core/src/main/java/de/uka/ilkd/key/proof/Proof.java index 06512119546..10e81f08614 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/proof/Proof.java +++ b/key.core/src/main/java/de/uka/ilkd/key/proof/Proof.java @@ -972,7 +972,7 @@ public boolean isOpenGoal(Node node) { * * @return the goal that belongs to the given node or null if the node is an inner one */ - public Goal getOpenGoal(@NonNull Node node) { + public @Nullable Goal getOpenGoal(@NonNull Node node) { for (final Goal result : openGoals) { if (result.node() == node) { return result; @@ -997,7 +997,7 @@ public boolean isClosedGoal(Node node) { * @return the closed goal that belongs to the given node or null if the node is an inner one or * an open goal */ - public Goal getClosedGoal(Node node) { + public @Nullable Goal getClosedGoal(Node node) { for (final Goal result : closedGoals) { if (result.node() == node) { return result; @@ -1006,6 +1006,20 @@ public Goal getClosedGoal(Node node) { return null; } + /** + * Get the goal (open or closed) belonging to the given node if it exists. + * + * @param node the Node where a corresponding goal is searched + * @return the goal that belongs to the given node or null if the node is an inner one + */ + public @Nullable Goal getGoal(Node node) { + Goal g = getOpenGoal(node); + if (g == null) { + g = getClosedGoal(node); + } + return g; + } + /** * returns the list of goals of the subtree starting with node. * diff --git a/key.core/src/main/java/de/uka/ilkd/key/proof/init/AbstractProfile.java b/key.core/src/main/java/de/uka/ilkd/key/proof/init/AbstractProfile.java index 83ac77ecdd7..eb1f56582c1 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/proof/init/AbstractProfile.java +++ b/key.core/src/main/java/de/uka/ilkd/key/proof/init/AbstractProfile.java @@ -61,6 +61,8 @@ protected AbstractProfile(String standardRuleFilename) { standardRules = new RuleCollection( RuleSourceFactory.fromDefaultLocation(standardRuleFilename), initBuiltInRules()); strategies = getStrategyFactories(); + // NPEs in tests revealed that strategies could contain null elements + assert !strategies.contains(null); this.supportedGCB = computeSupportedGoalChooserBuilder(); this.supportedGC = extractNames(supportedGCB); this.prototype = getDefaultGoalChooserBuilder(); diff --git a/key.core/src/main/java/de/uka/ilkd/key/proof/init/JavaProfile.java b/key.core/src/main/java/de/uka/ilkd/key/proof/init/JavaProfile.java index d91cbbd771c..c48012bbd11 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/proof/init/JavaProfile.java +++ b/key.core/src/main/java/de/uka/ilkd/key/proof/init/JavaProfile.java @@ -48,7 +48,24 @@ public class JavaProfile extends AbstractProfile { public static JavaProfile defaultInstance; public static JavaProfile defaultInstancePermissions; - public static final StrategyFactory DEFAULT = new JavaCardDLStrategyFactory(); + /** + * The default strategy factory to be used if no other strategy factory is + * specified. + * + * Caution: This used to be constructed at class load time, but cyclic reference between + * clauses made the field be read while the class was not yet fully initialized leading to + * null pointer exceptions. So we now use lazy initialization. + * + * (solution suggested by AW) + */ + private static StrategyFactory DEFAULT; + + public static StrategyFactory getDefault() { + if (DEFAULT == null) { + DEFAULT = new JavaCardDLStrategyFactory(); + } + return DEFAULT; + } private boolean permissions = false; @@ -121,7 +138,7 @@ protected ImmutableList computeTermLabelConfiguration() @Override protected ImmutableSet getStrategyFactories() { ImmutableSet set = super.getStrategyFactories(); - set = set.add(DEFAULT); + set = set.add(getDefault()); return set; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/proof/mgt/SpecificationRepository.java b/key.core/src/main/java/de/uka/ilkd/key/proof/mgt/SpecificationRepository.java index f907dd51fee..51c4a336cc8 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/proof/mgt/SpecificationRepository.java +++ b/key.core/src/main/java/de/uka/ilkd/key/proof/mgt/SpecificationRepository.java @@ -173,6 +173,7 @@ private static Taclet getLimitedToUnlimitedTaclet(IObserverFunction limited, ImmutableSLList.nil(), unlimitedTerm)); tacletBuilder.setName( MiscTools.toValidTacletName("unlimit " + getUniqueNameForObserver(unlimited))); + // tacletBuilder.addRuleSet(new RuleSet(new Name("unlimitObserver"))); return tacletBuilder.getTaclet(); } @@ -1879,10 +1880,12 @@ public JmlStatementSpec addStatementSpec(Statement statement, JmlStatementSpec s * list of terms, in * an immutable fasion. Updates require to create instances. *

- * Note: There is a immutability hole in {@link ProgramVariableCollection} due to mutable + * Note: There is an immutability hole in {@link ProgramVariableCollection} due to + * mutable * {@link Map} *

- * For {@link de.uka.ilkd.key.java.statement.JmlAssert} this is the formula behind the assert. + * For {@link de.uka.ilkd.key.java.statement.JmlAssert} this is the formula behind the assert + * (Potientially also containing the formulas within the optional proof). * For {@link de.uka.ilkd.key.java.statement.SetStatement} this is the target and the value * terms. * You may want to use the index constant for accessing them: @@ -1909,7 +1912,7 @@ public JTerm term(int index) { } /** - * Retrieve a term with a update to the given {@code self} term. + * Retrieve a term with an update to the given {@code self} term. * * @param services the corresponding services instance * @param self a term which describes the {@code self} object aka. this on the current diff --git a/key.core/src/main/java/de/uka/ilkd/key/prover/impl/DepthFirstGoalChooser.java b/key.core/src/main/java/de/uka/ilkd/key/prover/impl/DepthFirstGoalChooser.java index d76de6e176b..8e11b4ebd22 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/prover/impl/DepthFirstGoalChooser.java +++ b/key.core/src/main/java/de/uka/ilkd/key/prover/impl/DepthFirstGoalChooser.java @@ -74,6 +74,9 @@ protected void updateGoalListHelp(Object node, ImmutableList newGoals) { nextGoals = ImmutableSLList.nil(); + // Only consider automatic goals + newGoals = newGoals.filter(Goal::isAutomatic); + // Remove "node" and goals contained within "newGoals" while (!selectedList.isEmpty()) { final @NonNull Goal goal = selectedList.head(); diff --git a/key.core/src/main/java/de/uka/ilkd/key/rule/JmlAssertRule.java b/key.core/src/main/java/de/uka/ilkd/key/rule/JmlAssertRule.java index 6c82cdcb85c..77f35d755bc 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/rule/JmlAssertRule.java +++ b/key.core/src/main/java/de/uka/ilkd/key/rule/JmlAssertRule.java @@ -15,6 +15,11 @@ import de.uka.ilkd.key.logic.op.Transformer; import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.calculus.JavaDLSequentKit; +import de.uka.ilkd.key.proof.mgt.SpecificationRepository; +import de.uka.ilkd.key.rule.inst.SVInstantiations; +import de.uka.ilkd.key.rule.tacletbuilder.AntecSuccTacletGoalTemplate; +import de.uka.ilkd.key.rule.tacletbuilder.NoFindTacletBuilder; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement.Kind; import de.uka.ilkd.key.util.MiscTools; @@ -23,6 +28,7 @@ import org.key_project.prover.rules.RuleAbortException; import org.key_project.prover.rules.RuleApp; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.Sequent; import org.key_project.prover.sequent.SequentFormula; import org.key_project.util.collection.ImmutableList; @@ -132,7 +138,8 @@ public IBuiltInRuleApp createApp(PosInOccurrence occurrence, TermServices servic final MethodFrame frame = JavaTools.getInnermostMethodFrame(target.javaBlock(), services); final JTerm self = MiscTools.getSelfTerm(frame, services); - final var spec = services.getSpecificationRepository().getStatementSpec(jmlAssert); + final SpecificationRepository.JmlStatementSpec spec = + services.getSpecificationRepository().getStatementSpec(jmlAssert); if (spec == null) { throw new RuleAbortException( @@ -146,6 +153,8 @@ public IBuiltInRuleApp createApp(PosInOccurrence occurrence, TermServices servic kind == Kind.ASSERT ? OriginTermLabel.SpecType.ASSERT : OriginTermLabel.SpecType.ASSUME)); + final String label = jmlAssert.getOptLabel(); + final ImmutableList result; if (kind == Kind.ASSERT) { result = goal.split(2); @@ -156,7 +165,7 @@ public IBuiltInRuleApp createApp(PosInOccurrence occurrence, TermServices servic throw new RuleAbortException( String.format("Unknown assertion type %s", jmlAssert.getKind())); } - setUpUsageGoal(result.head(), occurrence, update, target, condition, tb, services); + setUpUsageGoal(result.head(), label, occurrence, update, target, condition, tb, services); return result; } @@ -168,7 +177,7 @@ private void setUpValidityRule(Goal goal, goal.changeFormula(new SequentFormula(tb.apply(update, condition)), occurrence); } - private void setUpUsageGoal(Goal goal, PosInOccurrence occurrence, + private void setUpUsageGoal(Goal goal, String label, PosInOccurrence occurrence, JTerm update, JTerm target, JTerm condition, TermBuilder tb, Services services) { goal.setBranchLabel("Usage"); @@ -178,6 +187,15 @@ private void setUpUsageGoal(Goal goal, PosInOccurrence occurrence, tb.prog(((Modality) target.op()).kind(), javaBlock, target.sub(0), null))); goal.changeFormula(new SequentFormula(newTerm), occurrence); + if (label != null) { + NoFindTacletBuilder bld = new NoFindTacletBuilder(); + Sequent ante = JavaDLSequentKit.createAnteSequent( + ImmutableList.of(new SequentFormula(tb.apply(update, condition)))); + bld.addTacletGoalTemplate(new AntecSuccTacletGoalTemplate(ante, ImmutableList.of(), + JavaDLSequentKit.getInstance().getEmptySequent())); + bld.setName(new Name("recall_" + label)); + goal.addTaclet(bld.getNoFindTaclet(), SVInstantiations.EMPTY_SVINSTANTIATIONS, false); + } } @Override diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AbstractCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AbstractCommand.java index d0badbb8e88..dd622d79b27 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AbstractCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AbstractCommand.java @@ -13,6 +13,7 @@ import de.uka.ilkd.key.scripts.meta.ArgumentsLifter; import de.uka.ilkd.key.scripts.meta.ProofScriptArgument; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -39,12 +40,17 @@ public abstract class AbstractCommand implements ProofScriptCommand { */ protected @Nullable String documentation = null; - protected final EngineState state() { + /** + * The state object of this engine. + */ + protected final @NonNull EngineState state() { return Objects.requireNonNull(state); } /** - * ... + * The POJO class of the parameter object, or null if this command does not take any parameters + * via + * a POJO. */ private final @Nullable Class parameterClazz; @@ -81,6 +87,8 @@ public final void execute(AbstractUserInterfaceControl uiControl, ScriptCommandA /// Executes the command logic with the given parameters `args`. /// + /// This is usually overridden by subclasses. + /// /// @param args an instance of the parameters /// @throws ScriptException if something happened during execution /// @throws InterruptedException if thread was interrupted during execution @@ -96,4 +104,8 @@ public String getDocumentation() { return Objects.requireNonNullElse(documentation, ""); } + @Override + public String getCategory() { + return ArgumentsLifter.extractCategory(getClass(), parameterClazz); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ActivateCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ActivateCommand.java index 4d04f87e091..244ce4bc9e9 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ActivateCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ActivateCommand.java @@ -5,6 +5,7 @@ import de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.scripts.meta.Documentation; /** * Command for re-activating the first open (not necessarily enabled) {@link Goal} after a "leave" @@ -13,17 +14,18 @@ * * @author Dominic Steinhoefel */ +@Documentation(category = "Control", value = """ + Reactivates the first open (not necessarily enabled) goal. + This can be useful after a 'leave' command to continue + working on a complicated proof where 'tryclose' should not + apply on certain branches temporarily, but where one still + wants to finish the proof.""") public class ActivateCommand extends NoArgumentCommand { @Override public String getName() { return "activate"; } - @Override - public String getDocumentation() { - return ""; - } - @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, EngineState state) diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AdditionalRulesStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AdditionalRulesStrategy.java new file mode 100644 index 00000000000..1ce2df56b39 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AdditionalRulesStrategy.java @@ -0,0 +1,132 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import de.uka.ilkd.key.macros.FilterStrategy; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.Taclet; +import de.uka.ilkd.key.strategy.Strategy; + +import org.key_project.logic.Name; +import org.key_project.prover.rules.Rule; +import org.key_project.prover.rules.RuleApp; +import org.key_project.prover.rules.RuleSet; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.NumberRuleAppCost; +import org.key_project.prover.strategy.costbased.RuleAppCost; +import org.key_project.prover.strategy.costbased.TopRuleAppCost; +import org.key_project.util.collection.Pair; + +import org.jspecify.annotations.Nullable; + +class AdditionalRulesStrategy extends FilterStrategy { + /** Name of that strategy */ + private static final Name NAME = new Name( + AdditionalRulesStrategy.class.getSimpleName()); + + private static final Map TRANSLATIONS = + Map.of("high", "-50", "medium", "1000", "low", "10000"); + private static final RuleAppCost DEFAULT_PRIORITY = NumberRuleAppCost.create(1000); + + private final List> additionalRules; + private final boolean exclusive; + + public AdditionalRulesStrategy(Strategy delegate, String additionalRules, boolean exclusive) { + super(delegate); + this.additionalRules = parseAddRules(additionalRules); + this.exclusive = exclusive; + } + + private List> parseAddRules(String additionalRules) { + List> result = new ArrayList<>(); + for (String entry : additionalRules.trim().split(" *, *")) { + String[] parts = entry.split(" *= *", 2); + RuleAppCost prio; + if (parts.length == 2) { + String prioStr = parts[1]; + if (prioStr.equals("off")) { + prio = TopRuleAppCost.INSTANCE; + } + prioStr = TRANSLATIONS.getOrDefault(prioStr, prioStr); + try { + prio = NumberRuleAppCost.create(Integer.parseInt(prioStr)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid value for additional rule: " + parts[1]); + } + } else { + prio = DEFAULT_PRIORITY; + } + + result.add(new Pair<>(parts[0], prio)); + } + return result; + } + + @Override + public Name name() { + return NAME; + } + + @Override + public RuleAppCost computeCost(RuleApp app, PosInOccurrence pio, Goal goal) { + RuleAppCost localCost = computeLocalCost(app.rule()); + if (localCost != null) { + return localCost; + } + return super.computeCost(app, pio, goal); + } + + @Override + public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { + RuleAppCost localCost = computeLocalCost(app.rule()); + if (localCost != null) { + return true; + } + if (exclusive) { + return false; + } else { + return super.isApprovedApp(app, pio, goal); + } + } + + private @Nullable RuleAppCost computeLocalCost(Rule rule) { + String name = rule.name().toString(); + Optional cost = lookup(name); + if (cost.isPresent()) { + return cost.get(); + } + + if (rule instanceof Taclet taclet) { + for (RuleSet rs : taclet.getRuleSets()) { + String rname = rs.name().toString(); + cost = lookup(rname); + if (cost.isPresent()) { + return cost.get(); + } + } + } + + return null; + } + + private Optional lookup(String name) { + return additionalRules.stream() + .filter(nameAndPrio -> name.matches(nameAndPrio.first)) + .findFirst() + .map(p -> p.second); + } + + @Override + public boolean isStopAtFirstNonCloseableGoal() { + return false; + } + + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java index d185a3af2c0..2d1f4012279 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java @@ -7,8 +7,18 @@ import de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.ProofScriptArgument; +@Documentation(category = "Control", value = """ + Executes a given block of script commands on all open goals. + The current goal is set to each open goal in turn while executing the block. + It expects exactly one positional argument, which is the block to be executed on each goal. + + #### Examples: + * `onAll { smt solver="z3"; }` + * `onAll { auto; }` + """) public class AllCommand implements ProofScriptCommand { @Override public List getArguments() { @@ -40,13 +50,4 @@ public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst arg public String getName() { return "onAll"; } - - /** - * {@inheritDoc} - */ - @Override - public String getDocumentation() { - return """ - Applies the given command to all the open goals."""; - } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java old mode 100755 new mode 100644 index 287f892cb49..8e5d9ba5e40 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java @@ -1,64 +1,35 @@ /* This file is part of KeY - https://key-project.org * KeY is licensed under the GNU General Public License Version 2 * SPDX-License-Identifier: GPL-2.0-only */ -package de.uka.ilkd.key.scripts; - - -import de.uka.ilkd.key.scripts.meta.Documentation; -import de.uka.ilkd.key.scripts.meta.Option; - -/** - * Halts the script if some condition is not met. - *

- * See exported documentation at {@link Parameters} at the end of this file. - * - * @author lanzinger - */ -public class AssertCommand extends AbstractCommand { - - /** - * Instantiates a new assert command. - */ - public AssertCommand() { - super(Parameters.class); - } - - @Override - public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { - var args = state().getValueInjector().inject(new Parameters(), arguments); - - if (args.goals == null) { - throw new ScriptException("No parameter specified!"); - } - - if (state().getProof().openEnabledGoals().size() != args.goals) { - throw new ScriptException("Assertion failed: number of open goals is " - + state().getProof().openGoals().size() + ", but should be " + args.goals); - } - } - - @Override - public String getName() { - return "assert"; - } - - /** - * The Assigned parameters (currently only the passed goals). - */ - @Documentation(""" - The assert command checks if the number of open and enabled goals is equal to the given number. - If not, the script is halted with an error message. - - Deprecated: This command is deprecated and should not be used in new scripts. - The name of this command is likely to change since "assert" will - be used for a more general purpose. You may find that this is called - "failUnless". - """) - public static class Parameters { - /** - * The number of open and enabled goals. - */ - @Option("goals") - public Integer goals; - } -} +/// * This file is part of KeY - https://key-project.org +// * KeY is licensed under the GNU General Public License Version 2 +// * SPDX-License-Identifier: GPL-2.0-only */ +// package de.uka.ilkd.key.scripts; +// +/// ** +// * An assertion which essentially performs a cut. +// * +// * The only difference is that this implementation tampers with the labels of the resulting goals +/// to +// * allow them to be +// * better recognized in the script engine. +// * +// * (Unlike in other systems, in KeY the assertion does not remove the original goal formula since +// * that is not well-defined in sequent calculus.) +// */ +// public class AssertCommand extends CutCommand { +// +// @Override +// public String getName() { +// return "assert"; +// } +// +// @Override +// public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { +// var args = state().getValueInjector().inject(new Parameters(), arguments); +// var node = state().getFirstOpenAutomaticGoal().node(); +// execute(state(), args); +// // node.proof().getGoal(node.child(0)).setBranchLabel("Validity"); +// // node.proof().getGoal(node.child(1)).setBranchLabel("Usage"); +// } +// } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java new file mode 100755 index 00000000000..d4821e8c04a --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java @@ -0,0 +1,62 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + + +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.Option; + +/** + * Halts the script if the expected number of open and enabled goals is not met. + *

+ * See exported documentation at {@link Parameters} at the end of this file. + * + * @author lanzinger + */ +public class AssertOpenGoalsCommand extends AbstractCommand { + + /** + * Instantiates a new assert command. + */ + public AssertOpenGoalsCommand() { + super(Parameters.class); + } + + @Override + public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new Parameters(), arguments); + + if (args.goals == null) { + throw new ScriptException("No parameter specified!"); + } + + if (state().getProof().openEnabledGoals().size() != args.goals) { + throw new ScriptException("Assertion failed: number of open goals is " + + state().getProof().openGoals().size() + ", but should be " + args.goals); + } + } + + @Override + public String getName() { + return "assertOpenGoals"; + } + + /** + * The Assigned parameters (currently only the passed goals). + */ + @Documentation(category = "Control", + value = """ + The assert command checks if the number of open and enabled goals is equal to the given number. + If not, the script is halted with an error message. + + Note: This command was called "assert" originally. + """) + public static class Parameters { + /** + * The number of open and enabled goals. + */ + @Option("goals") + public Integer goals; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssumeCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssumeCommand.java index 27e2d753301..5781941f9eb 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssumeCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssumeCommand.java @@ -4,14 +4,27 @@ package de.uka.ilkd.key.scripts; import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.TermFactory; +import de.uka.ilkd.key.logic.op.SchemaVariableFactory; +import de.uka.ilkd.key.proof.calculus.JavaDLSequentKit; +import de.uka.ilkd.key.rule.NoFindTaclet; import de.uka.ilkd.key.rule.NoPosTacletApp; import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.rule.tacletbuilder.TacletGoalTemplate; import de.uka.ilkd.key.scripts.meta.Argument; import de.uka.ilkd.key.scripts.meta.Documentation; +import org.key_project.logic.ChoiceExpr; import org.key_project.logic.Name; import org.key_project.logic.op.sv.SchemaVariable; +import org.key_project.prover.rules.ApplicationRestriction; +import org.key_project.prover.rules.TacletApplPart; +import org.key_project.prover.rules.TacletAttributes; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.DefaultImmutableMap; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSet; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.NullMarked; @@ -23,7 +36,32 @@ */ @NullMarked public class AssumeCommand extends AbstractCommand { - private static final Name TACLET_NAME = new Name("UNSOUND_ASSUME"); + + /** + * The taclet for the assume command is a local constructed taclet that is not available in the + * usual + * KeY taclet base. This is on purpose. Proofs can be conducted and saved with this command, + * but they cannot be reloaded. + */ + private static final Taclet ASSUME_TACLET; + + static { + SchemaVariable sv = SchemaVariableFactory.createFormulaSV(new Name("condition")); + JavaDLSequentKit kit = JavaDLSequentKit.getInstance(); + TacletApplPart applPart = + new TacletApplPart(kit.getEmptySequent(), + new ApplicationRestriction(ApplicationRestriction.IN_SEQUENT_STATE), + ImmutableList.of(), + ImmutableList.of(), ImmutableList.of(), ImmutableList.of()); + SequentFormula sf = new SequentFormula(new TermFactory().createTerm(sv)); + TacletGoalTemplate goal = new TacletGoalTemplate(kit.newAntecedent(ImmutableList.of(sf)), + ImmutableList.of(), ImmutableSet.of()); + ASSUME_TACLET = + new NoFindTaclet(new Name("CHEAT_ASSUME"), applPart, ImmutableList.of(goal), + ImmutableList.of(), + new TacletAttributes("assume", null), DefaultImmutableMap.nilMap(), ChoiceExpr.TRUE, + ImmutableSet.empty()); + } public AssumeCommand() { super(FormulaParameter.class); @@ -34,22 +72,10 @@ public String getName() { return "assume"; } - @Override - public String getDocumentation() { - return """ - The assume command is an unsound taclet rule and takes one argument: - - The command adds the formula passed as argument to the antecedent - a formula #2 to which the command is applied"""; - } - - public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { var parameter = state().getValueInjector() .inject(new FormulaParameter(), arguments); - Taclet cut = - state.getProof().getEnv().getInitConfigForEnvironment().lookupActiveTaclet(TACLET_NAME); - TacletApp app = NoPosTacletApp.createNoPosTacletApp(cut); + TacletApp app = NoPosTacletApp.createNoPosTacletApp(ASSUME_TACLET); SchemaVariable sv = app.uninstantiatedVars().iterator().next(); app = app.addCheckedInstantiation(sv, parameter.formula, state.getProof().getServices(), @@ -57,6 +83,12 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup state.getFirstOpenAutomaticGoal().apply(app); } + @Documentation(category = "Control", + value = """ + The assume command is an **unsound** taclet rule and adds a formula to the antecedent of the current goal + Can be used for debug and proof exploration purposes. Proof files for proofs with this command cannot + be reloaded. + """) public static class FormulaParameter { @Argument @Documentation("The formula to be assumed.") diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AutoCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AutoCommand.java index 5a92b99b744..2d259dd1023 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AutoCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AutoCommand.java @@ -12,10 +12,9 @@ import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.proof.init.Profile; import de.uka.ilkd.key.prover.impl.ApplyStrategy; -import de.uka.ilkd.key.scripts.meta.Documentation; -import de.uka.ilkd.key.scripts.meta.Flag; -import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.scripts.meta.*; import de.uka.ilkd.key.strategy.FocussedBreakpointRuleApplicationManager; +import de.uka.ilkd.key.strategy.Strategy; import de.uka.ilkd.key.strategy.StrategyProperties; import org.key_project.prover.engine.ProverCore; @@ -48,11 +47,6 @@ public String getName() { return "auto"; } - @Override - public String getDocumentation() { - return "The AutoCommand invokes the automatic strategy \"Auto\""; - } - @Override public void execute(ScriptCommandAst args) throws ScriptException, InterruptedException { var arguments = state().getValueInjector().inject(new AutoCommand.Parameters(), args); @@ -69,7 +63,7 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx goals = state().getProof().openGoals(); } else { final Goal goal = state().getFirstOpenAutomaticGoal(); - goals = ImmutableSLList.nil().prepend(goal); + goals = ImmutableList.of(goal); if (arguments.matches != null || arguments.breakpoint != null) { setupFocussedBreakpointStrategy( // @@ -79,8 +73,8 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx // set the max number of steps if given int oldNumberOfSteps = state().getMaxAutomaticSteps(); - if (arguments.getSteps() > 0) { - state().setMaxAutomaticSteps(arguments.getSteps()); + if (arguments.maxSteps > 0) { + state().setMaxAutomaticSteps(arguments.maxSteps); } // set model search if given @@ -99,6 +93,16 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx SetCommand.updateStrategySettings(state(), activeStrategyProperties); + final Strategy originalStrategy = state.getProof().getActiveStrategy(); + if (arguments.additionalRules != null) { + state.getProof().setActiveStrategy( + new AdditionalRulesStrategy(originalStrategy, arguments.additionalRules, false)); + } + if (arguments.onlyRules != null) { + state.getProof().setActiveStrategy( + new AdditionalRulesStrategy(originalStrategy, arguments.onlyRules, true)); + } + // Give some feedback applyStrategy.addProverTaskObserver(uiControl); @@ -120,6 +124,7 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx activeStrategyProperties.setProperty(ov.settingName, ov.oldValue); } } + state.getProof().setActiveStrategy(originalStrategy); SetCommand.updateStrategySettings(state(), activeStrategyProperties); } } @@ -134,7 +139,7 @@ private Map prepareOriginalValues() { res.put("classAxioms", new OriginalValue(CLASS_AXIOM_OPTIONS_KEY, CLASS_AXIOM_FREE, CLASS_AXIOM_OFF)); res.put("dependencies", new OriginalValue(DEP_OPTIONS_KEY, DEP_ON, DEP_OFF)); - // ... add further (boolean for the moment) setings here. + // ... add further (boolean for the moment) settings here. return res; } @@ -168,50 +173,54 @@ private void setupFocussedBreakpointStrategy(final String maybeMatchesRegEx, new AbstractProofControl.FocussedAutoModeTaskListener(services.getProof())); } - @Documentation(""" - The AutoCommand is a command that invokes the automatic strategy "Auto" of KeY. - It can be used to automatically prove a goal or a set of goals. - Use with care, as this command may leave the proof state in an unpredictable state + @Documentation(category = "Fundamental", value = """ + The AutoCommand invokes the automatic strategy "Auto" of KeY (which is also launched by + when clicking the "Auto" button in the GUI). + It can be used to try to automatically prove the current goal. + Use with care, as this command may leave the proof in a incomprehensible state with many open goals. Use the command with "close" to make sure the command succeeds for fails without changes.""") - public static class Parameters { + public static class Parameters implements ValueInjector.VerifyableParameters { // @ TODO Deprecated with the higher order proof commands? @Flag(value = "all") - @Documentation("Apply the strategy on all open goals. There is a better syntax for that now.") + @Documentation("*Deprecated*. Apply the strategy on all open goals. There is a better syntax for that now.") public boolean onAllOpenGoals = false; @Option(value = "steps") - @Documentation("The maximum number of steps to be performed.") - public int maxSteps = -1; + @Documentation("The maximum number of proof steps to be performed.") + public @Nullable int maxSteps = -1; /** * Run on formula matching the given regex */ @Option(value = "matches") - @Documentation("Run on formula matching the given regex.") + @Documentation("Run on the formula matching the given regex.") public @Nullable String matches = null; /** * Run on formula matching the given regex */ @Option(value = "breakpoint") - @Documentation("Run on formula matching the given regex.") + @Documentation("When doing symbolic execution by auto, this option can be used to set a Java statement at which " + + + "symbolic execution has to stop.") public @Nullable String breakpoint = null; @Flag(value = "modelsearch") - @Documentation("Enable model search. Better for some types of arithmetic problems. Sometimes a lot worse") + @Documentation("Enable model search. Better for some (types of) arithmetic problems. Sometimes a lot worse.") public boolean modelSearch; @Flag(value = "expandQueries") - @Documentation("Expand queries by modalities.") + @Documentation("Automatically expand occurrences of query symbols using additional modalities on the sequent.") public boolean expandQueries; @Flag(value = "classAxioms") @Documentation(""" - Enable class axioms. This expands model methods and fields and invariants quite eagerly. \ - May lead to divergence.""") + Enable automatic and eager expansion of symbols. This expands class invariants, model methods and + fields and invariants quite eagerly. May be an enabler (if a few definitions need to expanded), + may be a showstopper (if expansion increases the complexity on the sequent too much).""") public boolean classAxioms; @Flag(value = "dependencies") @@ -220,8 +229,30 @@ public static class Parameters { without that its definition is known. May be an enabler, may be a showstopper.""") public boolean dependencies; - public int getSteps() { - return maxSteps; + @Option(value = "add") + @Documentation(""" + Additional rules to be used by the auto strategy. The rules have to be given as a + comma-separated list of rule names and rule set names. Each entry can be assigned to a priority + (high, low, medium or a natural number) using an equals sign. + Cannot be combined with the 'only' parameter. + """) + public @Nullable String additionalRules; + + @Option(value = "only") + @Documentation(""" + Limit the rules to be used by the auto strategy. The rules have to be given as a + comma-separated list of rule names and rule set names. Each entry can be assigned to a priority + (high, low, medium or a natural number) using an equals sign. + All rules application which do not match the given names will be disabled. + Cannot be combined with the 'add' parameter. + """) + public @Nullable String onlyRules; + + @Override + public void verifyParameters() throws IllegalArgumentException, InjectionException { + if (onlyRules != null && additionalRules != null) { + throw new InjectionException("Parameters 'add' and 'only' are mutually exclusive."); + } } } @@ -229,7 +260,7 @@ private static final class OriginalValue { private final String settingName; private final String trueValue; private final String falseValue; - private String oldValue; + private @Nullable String oldValue; private OriginalValue(String settingName, String trueValue, String falseValue) { this.settingName = settingName; diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/AxiomCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AxiomCommand.java index 182f94916ec..c12d723c08d 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AxiomCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AxiomCommand.java @@ -9,6 +9,7 @@ import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.op.sv.SchemaVariable; @@ -25,6 +26,10 @@ * Use the {@link AssumeCommand} "assume" instead. */ @Deprecated(forRemoval = true) +@Documentation(""" + This command is deprecated and should not be used in new scripts. + Use the equivalent `assume` command instead. + """) public class AxiomCommand extends AssumeCommand { private static final Name TACLET_NAME = new Name("cut"); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java new file mode 100644 index 00000000000..2bc0a829b88 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java @@ -0,0 +1,161 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Stack; + +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.Node; +import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.key_project.prover.rules.tacletbuilder.TacletGoalTemplate; +import org.key_project.util.collection.ImmutableList; + +import org.jspecify.annotations.Nullable; + +public class BranchesCommand extends AbstractCommand { + + public BranchesCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "branches"; + } + + @Override + public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new BranchesCommand.Parameters(), arguments); + + Stack stack = (Stack) state.getUserData("_branchStack"); + if (stack == null) { + stack = new Stack<>(); + state.putUserData("_branchStack", stack); + } + + if (args.mode == null) { + throw new ScriptException("For 'branches', a mode must be specified"); + } + + switch (args.mode) { + case "push": + ensureSingleGoal(); + Node node = state.getFirstOpenAutomaticGoal().node(); + // this is the first goal. The parent is the decision point + stack.push(node.serialNr()); + break; + case "pop": + stack.pop(); + break; + case "select": + Node root = findNodeByNumber(proof, stack.peek()); + Goal goal; + if (args.branch == null) { + goal = findGoalByNode(state.getProof(), root.child(args.child)); + } else { + goal = findGoalByName(root, args.branch); + } + state.setGoal(goal); + break; + case "single": + root = findNodeByNumber(proof, stack.peek()); + TacletApp ta = (TacletApp) root.getAppliedRuleApp(); + ImmutableList templates = ta.taclet().goalTemplates(); + + int no = 0; + int found = -1; + for (TacletGoalTemplate template : templates) { + if (!"main".equals(template.tag())) { + if (found != -1) { + throw new ScriptException("More than one non-main goal found"); + } + found = no; + } + no++; + } + if (found == -1) { + throw new ScriptException("No single non-main goal found"); + } + + // For some reason, the child index is reversed between the node and the templates + found = templates.size() - 1 - found; + goal = findGoalByNode(proof, root.child(found)); + state.setGoal(goal); + break; + default: + throw new ScriptException( + "Unknown mode " + args.mode + " for the 'branches' command"); + } + } + + private void ensureSingleGoal() { + // state. + } + + private Goal findGoalByName(Node root, String branch) throws ScriptException { + Iterator it = root.childrenIterator(); + List knownBranchLabels = new ArrayList<>(); + int number = 1; + while (it.hasNext()) { + Node node = it.next(); + String label = node.getNodeInfo().getBranchLabel(); + if (label == null) { + label = "Case " + number; + } + knownBranchLabels.add(label); + if (branch.equals(label)) { + return findGoalByNode(root.proof(), node); + } + number++; + } + throw new ScriptException( + "Unknown branch " + branch + ". Known branches are " + knownBranchLabels); + } + + private static Goal findGoalByNode(Proof proof, Node node) throws ScriptException { + Optional result = + proof.openEnabledGoals().stream().filter(g -> g.node() == node).findAny(); + if (result.isEmpty()) { + throw new ScriptException(); + } + return result.get(); + } + + private Node findNodeByNumber(Proof proof, int serial) throws ScriptException { + Deque todo = new LinkedList<>(); + todo.add(proof.root()); + while (!todo.isEmpty()) { + Node n = todo.remove(); + if (n.serialNr() == serial) { + return n; + } + Iterator it = n.childrenIterator(); + while (it.hasNext()) { + todo.add(it.next()); + } + } + throw new ScriptException(); + } + + public static class Parameters { + /** A formula defining the goal to select */ + @Argument + public String mode; + @Option(value = "branch") + public @Nullable String branch; + @Option(value = "child") + public @Nullable Integer child; + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java new file mode 100644 index 00000000000..0e1a0959044 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java @@ -0,0 +1,55 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.proof.calculus.JavaDLSequentKit; +import de.uka.ilkd.key.rule.NoFindTaclet; +import de.uka.ilkd.key.rule.NoPosTacletApp; +import de.uka.ilkd.key.rule.Taclet; +import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Documentation; + +import org.key_project.logic.ChoiceExpr; +import org.key_project.logic.Name; +import org.key_project.prover.rules.ApplicationRestriction; +import org.key_project.prover.rules.TacletApplPart; +import org.key_project.prover.rules.TacletAttributes; +import org.key_project.util.collection.DefaultImmutableMap; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSet; + +@Documentation(category = "Internal", value = """ + Use this to close a goal unconditionally. This is unsound and should only + be used for testing and proof debugging purposes. It is similar to 'sorry' + in Isabelle or 'admit' in Rocq. + """) +public class CheatCommand extends NoArgumentCommand { + private static final Taclet CHEAT_TACLET; + + static { + TacletApplPart applPart = + new TacletApplPart(JavaDLSequentKit.getInstance().getEmptySequent(), + new ApplicationRestriction(ApplicationRestriction.IN_SEQUENT_STATE), + ImmutableList.of(), + ImmutableList.of(), ImmutableList.of(), ImmutableList.of()); + CHEAT_TACLET = + new NoFindTaclet(new Name("CHEAT"), applPart, ImmutableList.of(), ImmutableList.of(), + new TacletAttributes("cheat", null), DefaultImmutableMap.nilMap(), ChoiceExpr.TRUE, + ImmutableSet.empty()); + } + + @Override + public String getName() { + return "cheat"; + } + + @Override + public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst ast, + EngineState state) + throws ScriptException, InterruptedException { + TacletApp app = NoPosTacletApp.createNoPosTacletApp(CHEAT_TACLET); + state.getFirstOpenAutomaticGoal().apply(app); + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/CutCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/CutCommand.java index 454393b0fb8..7fb7396fc97 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/CutCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/CutCommand.java @@ -3,11 +3,14 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.scripts; +import java.util.List; + import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.rule.NoPosTacletApp; import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.op.sv.SchemaVariable; @@ -30,13 +33,10 @@ public String getName() { return "cut"; } + // From within JML scripts, "assert" is more common than "cut" @Override - public String getDocumentation() { - return """ - CutCommand has as script command name "cut" - - As parameters: - * a formula with the id "#2"""; + public List getAliases() { + return List.of(getName(), "assert"); } @Override @@ -51,13 +51,22 @@ static void execute(EngineState state, Parameters args) throws ScriptException { TacletApp app = NoPosTacletApp.createNoPosTacletApp(cut); SchemaVariable sv = app.uninstantiatedVars().iterator().next(); - app = app.addCheckedInstantiation(sv, args.formula, - state.getProof().getServices(), true); + var formula = + state.getProof().getServices().getTermBuilder().convertToFormula(args.formula); + + app = app.addCheckedInstantiation(sv, formula, state.getProof().getServices(), true); state.getFirstOpenAutomaticGoal().apply(app); } + @Documentation(category = "Fundamental", value = """ + The cut command makes a case distinction (a cut) on a formula on the current proof goal. + From within JML scripts, the alias 'assert' is more common than using 'cut'. + If followed by a `\\by proof` suffix in JML, it refers the sequent where + the cut formula is introduced to the succedent (i.e. where it is to be established). + """) public static class Parameters { @Argument + @Documentation("The formula to make the case distinction on.") public @MonotonicNonNull JTerm formula; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/DependencyContractCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/DependencyContractCommand.java new file mode 100644 index 00000000000..4cfbcf323e7 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/DependencyContractCommand.java @@ -0,0 +1,132 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.ArrayList; +import java.util.List; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.rule.UseDependencyContractApp; +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.key_project.logic.PosInTerm; +import org.key_project.logic.Term; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.Sequent; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableArray; +import org.key_project.util.collection.ImmutableList; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.jspecify.annotations.Nullable; + +/** + * The DependencyContractCommand applies a dependency contract to a selected formula in the current + * goal. See documentation of {@link Parameters} for more information. + */ +public class DependencyContractCommand extends AbstractCommand { + + public DependencyContractCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "dependency"; + } + + @Override + public void execute(ScriptCommandAst command) throws ScriptException, InterruptedException { + + Parameters arguments = state().getValueInjector().inject(new Parameters(), command); + + final Goal goal = state.getFirstOpenAutomaticGoal(); + + if (arguments.heap == null) { + Services services = goal.proof().getServices(); + arguments.heap = services.getTermFactory() + .createTerm(services.getTypeConverter().getHeapLDT().getHeap()); + } + + List pios = find(arguments.on, goal.sequent()); + + if (pios.isEmpty()) { + throw new ScriptException("dependency contract not applicable."); + } else if (pios.size() > 1) { + throw new ScriptException("no unique application"); + } + + PosInOccurrence pio = pios.get(0); + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, pio); + for (IBuiltInRuleApp builtin : builtins) { + if (builtin instanceof UseDependencyContractApp) { + apply(goal, (UseDependencyContractApp) builtin, arguments); + } + } + + } + + private List find(JTerm term, Sequent sequent) { + List pios = new ArrayList<>(); + for (SequentFormula sf : sequent.antecedent()) { + PosInOccurrence pio = new PosInOccurrence(sf, PosInTerm.getTopLevel(), true); + find(pios, term, pio); + } + + for (SequentFormula sf : sequent.succedent()) { + PosInOccurrence pio = new PosInOccurrence(sf, PosInTerm.getTopLevel(), false); + find(pios, term, pio); + } + return pios; + } + + private void find(List pios, JTerm term, PosInOccurrence pio) { + Term subTerm = pio.subTerm(); + if (term.equals(subTerm)) { + pios.add(pio); + } else { + ImmutableArray subs = subTerm.subs(); + for (int i = 0; i < subs.size(); i++) { + find(pios, term, pio.down(i)); + } + } + } + + private void apply(Goal goal, UseDependencyContractApp ruleApp, Parameters arguments) { + JTerm on = arguments.on; + JTerm[] subs = on.subs().toArray(new JTerm[0]); + subs[0] = arguments.heap; + Services services = goal.proof().getServices(); + JTerm replaced = + services.getTermFactory().createTerm(on.op(), subs, on.boundVars(), on.getLabels()); + List pios = find(replaced, goal.sequent()); + ruleApp = ruleApp.setStep(pios.get(0)); + ruleApp = ruleApp.tryToInstantiateContract(services); + goal.apply(ruleApp); + } + + @Documentation(category = "Fundamental", + value = """ + The dependency command applies a dependency contract to a specified term in the current goal. + Dependency contracts allow you to do modular reasoning. If for a heap-dependent function symbol, + no changes occur inside the dependency set of this function, the result remains the same. + This can be applied to model methods, model fields or invariants. + """) + public static class Parameters { + + @Documentation("The term to which the dependency contract should be applied. " + + "This term must occur in the current goal. " + + "And it must be the invocation of a heap-dependent observer function symbol.") + @Option(value = "on") + public @MonotonicNonNull JTerm on; + + @Documentation("The heap term to be compared against. If not given, the default heap is used.") + @Option(value = "heap") + public @Nullable JTerm heap; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/EchoCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/EchoCommand.java index 306a7d43c32..b9ef830fcbe 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/EchoCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/EchoCommand.java @@ -5,6 +5,7 @@ import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -32,11 +33,13 @@ public void execute(ScriptCommandAst args) } } + @Documentation(category = "Control", value = """ + A simple "print" command for giving progress feedback to the + human verfier during lengthy executions. + """) public static class Parameters { - /** - * The message to show. - */ @Argument + @Documentation("The message to be printed.") public @MonotonicNonNull String message; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java index 848559a302e..2201cde2e70 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Deque; +import java.util.HashMap; import java.util.LinkedList; import java.util.Objects; import java.util.Optional; @@ -14,24 +15,18 @@ import de.uka.ilkd.key.java.Services; import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.NamespaceSet; -import de.uka.ilkd.key.nparser.KeYParser.ProofScriptExpressionContext; import de.uka.ilkd.key.nparser.KeyIO; import de.uka.ilkd.key.parser.ParserException; import de.uka.ilkd.key.pp.AbbrevMap; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Node; import de.uka.ilkd.key.proof.Proof; -import de.uka.ilkd.key.scripts.meta.ConversionException; -import de.uka.ilkd.key.scripts.meta.Converter; -import de.uka.ilkd.key.scripts.meta.NoSpecifiedConverterException; import de.uka.ilkd.key.scripts.meta.ValueInjector; import de.uka.ilkd.key.settings.ProofSettings; import org.key_project.logic.sort.Sort; -import org.key_project.prover.sequent.Semisequent; import org.key_project.prover.sequent.Sequent; import org.key_project.util.collection.ImmutableList; -import org.key_project.util.java.StringUtil; import org.antlr.v4.runtime.CharStreams; import org.jspecify.annotations.NonNull; @@ -53,7 +48,6 @@ public class EngineState { private final AbbrevMap abbrevMap = new AbbrevMap(); private final ValueInjector valueInjector = createDefaultValueInjector(); - private final ExprEvaluator exprEvaluator = new ExprEvaluator(this); private @Nullable Consumer observer; private Path baseFileName = Paths.get("."); @@ -61,6 +55,8 @@ public class EngineState { private @Nullable Goal goal; private @Nullable Node lastSetGoalNode; + private final HashMap userData = new HashMap<>(); + /** * If set to true, outputs all commands to observers and console. Otherwise, only shows explicit * echo messages. @@ -78,61 +74,31 @@ public EngineState(Proof proof, ProofScriptEngine engine) { this.engine = engine; } + /// add converters for types used in proof scripts, + /// add support for parse trees private ValueInjector createDefaultValueInjector() { - var v = ValueInjector.createDefault(); + ValueInjector v = ValueInjector.createDefault(); + + // from string to ... v.addConverter(JTerm.class, String.class, (str) -> this.toTerm(str, null)); v.addConverter(Sequent.class, String.class, this::toSequent); v.addConverter(Sort.class, String.class, this::toSort); - addContextTranslator(v, String.class); - addContextTranslator(v, JTerm.class); - addContextTranslator(v, Integer.class); - addContextTranslator(v, Byte.class); - addContextTranslator(v, Long.class); - addContextTranslator(v, Boolean.class); - addContextTranslator(v, Character.class); - addContextTranslator(v, Sequent.class); - addContextTranslator(v, Integer.TYPE); - addContextTranslator(v, Byte.TYPE); - addContextTranslator(v, Long.TYPE); - addContextTranslator(v, Boolean.TYPE); - addContextTranslator(v, Character.TYPE); - addContextTranslator(v, JTerm.class); - addContextTranslator(v, Sequent.class); - addContextTranslator(v, Semisequent.class); - addContextTranslator(v, ScriptBlock.class); - return v; - } + // to terms with holes + v.addConverter(TermWithHoles.class, String.class, + str -> TermWithHoles.fromString(this, str)); - private void addContextTranslator(ValueInjector v, Class aClass) { - Converter converter = - (ProofScriptExpressionContext a) -> convertToString(v, aClass, a); - v.addConverter(aClass, ProofScriptExpressionContext.class, converter); + // to sequents with holes + v.addConverter(SequentWithHoles.class, String.class, + str -> SequentWithHoles.fromString(this, str)); + + // from KeY parse tree to everything + ExprEvaluator exprEvaluator = new ExprEvaluator(this); + exprEvaluator.addConvertersToValueInjector(v); + return v; } @SuppressWarnings("unchecked") - private R convertToString(ValueInjector inj, Class aClass, - ProofScriptExpressionContext ctx) - throws Exception { - try { - if (aClass == String.class && ctx.string_literal() != null) { - return inj.getConverter(aClass, String.class) - .convert(StringUtil.trim(ctx.string_literal().getText(), '"')); - } - if (aClass == String.class) { - return inj.getConverter(aClass, String.class).convert(ctx.getText()); - } - - T value = (T) ctx.accept(exprEvaluator); - Class tClass = (Class) value.getClass(); - if (aClass.isAssignableFrom(value.getClass())) { - return aClass.cast(value); - } - return inj.getConverter(aClass, tClass).convert(value); - } catch (ConversionException | NoSpecifiedConverterException e) { - return inj.getConverter(aClass, String.class).convert(ctx.getText()); - } - } protected static Goal getGoal(ImmutableList openGoals, Node node) { for (Goal goal : openGoals) { @@ -143,7 +109,7 @@ protected static Goal getGoal(ImmutableList openGoals, Node node) { return null; } - public void setGoal(Goal g) { + public void setGoal(@Nullable Goal g) { goal = g; lastSetGoalNode = Optional.ofNullable(g).map(Goal::node).orElse(null); } @@ -359,7 +325,11 @@ public NamespaceSet getCurrentNamespaces() { } } - public ExprEvaluator getEvaluator() { - return exprEvaluator; + public void putUserData(String key, @Nullable Object val) { + userData.put(key, val); + } + + public @Nullable Object getUserData(String key) { + return userData.get(key); } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ExitCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExitCommand.java index a3c79c478f5..3b4fbf8a9d0 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ExitCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExitCommand.java @@ -5,7 +5,12 @@ import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.scripts.meta.Documentation; +@Documentation(category = "Control", value = """ + Exits the currently running script context unconditionally. + (In the future, there may try-catch blocks to react to this). + """) public class ExitCommand extends NoArgumentCommand { @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, @@ -18,9 +23,4 @@ public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst arg public String getName() { return "exit"; } - - @Override - public String getDocumentation() { - return "Kills the script execution."; - } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ExpandDefCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExpandDefCommand.java new file mode 100644 index 00000000000..295dff68096 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExpandDefCommand.java @@ -0,0 +1,122 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.rule.PosTacletApp; +import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.key_project.logic.PosInTerm; +import org.key_project.prover.proof.rulefilter.TacletFilter; +import org.key_project.prover.rules.Taclet; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; + +import org.jspecify.annotations.Nullable; + +public class ExpandDefCommand extends AbstractCommand { + + private static final ExpansionFilter FILTER = new ExpansionFilter(); + + public ExpandDefCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "expand"; + } + + @Override + public void execute(ScriptCommandAst command) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new Parameters(), command); + Goal g = state().getFirstOpenAutomaticGoal(); + TacletApp theApp = makeRuleApp(args, state()); + + ImmutableList completions = + theApp.findIfFormulaInstantiations(g.sequent(), g.proof().getServices()); + if (completions == null || completions.isEmpty()) { + throw new ScriptException("Cannot complete the rule app"); + } + + TacletApp app = completions.head(); + app = app.tryToInstantiate(g.proof().getServices().getOverlay(g.getLocalNamespaces())); + if (app == null || !app.complete()) { + throw new ScriptException("Cannot complete the rule app"); + } + + g.apply(app); + + } + + private TacletApp makeRuleApp(Parameters p, EngineState state) throws ScriptException { + + Goal g = state.getFirstOpenAutomaticGoal(); + Proof proof = state.getProof(); + + ImmutableList apps = ImmutableList.of(); + for (SequentFormula anteForm : g.sequent().antecedent()) { + apps = apps.prepend(g.ruleAppIndex().getTacletAppAtAndBelow(FILTER, + new PosInOccurrence(anteForm, PosInTerm.getTopLevel(), true), proof.getServices())); + } + + for (SequentFormula succForm : g.sequent().succedent()) { + apps = apps.prepend(g.ruleAppIndex().getTacletAppAtAndBelow(FILTER, + new PosInOccurrence(succForm, PosInTerm.getTopLevel(), false), + proof.getServices())); + } + + if (p.on != null) { + apps = apps.filter( + it -> it instanceof PosTacletApp && + p.on.matches(it.posInOccurrence())); + } else if (p.formula != null) { + apps = apps.filter( + it -> it instanceof PosTacletApp && + p.formula.matchesToplevel(it.posInOccurrence().sequentFormula())); + } else { + throw new ScriptException("Either 'formula' or 'on' must be specified"); + } + + if (apps.isEmpty()) { + throw new ScriptException("There is no expansion rule app that matches 'on'"); + } else if (p.occ != null && p.occ >= 0) { + if (p.occ >= apps.size()) { + throw new ScriptException( + "The 'occ' parameter is beyond the number of occurrences."); + } + return apps.get(p.occ); + } else { + if (apps.size() != 1) { + throw new ScriptException("The 'on' parameter is not unique"); + } + return apps.head(); + } + + } + + public static class Parameters { + @Option(value = "on") + public @Nullable TermWithHoles on; + @Option(value = "occ") + public @Nullable Integer occ; + @Option(value = "formula") + public @Nullable TermWithHoles formula; + + } + + private static class ExpansionFilter extends TacletFilter { + + @Override + protected boolean filter(Taclet taclet) { + String name = taclet.name().toString(); + return name.startsWith("Class_invariant_axiom_for") || + name.startsWith("Definition_axiom_for"); + } + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ExprEvaluator.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExprEvaluator.java index 4862f59dcad..4d380292023 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ExprEvaluator.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExprEvaluator.java @@ -5,23 +5,27 @@ import java.net.URI; -import de.uka.ilkd.key.nparser.KeYParser; +import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.nparser.KeYParser.*; -import de.uka.ilkd.key.nparser.KeYParserBaseVisitor; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.nparser.builder.ExpressionBuilder; +import de.uka.ilkd.key.proof.calculus.JavaDLSequentKit; +import de.uka.ilkd.key.scripts.meta.ConversionException; +import de.uka.ilkd.key.scripts.meta.ValueInjector; +import de.uka.ilkd.key.util.ANTLRUtil; import org.key_project.prover.sequent.Sequent; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.java.StringUtil; import org.antlr.v4.runtime.ParserRuleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.key_project.util.java.StringUtil.trim; /// Evaluates expression inside of proof script to their appropriate type. /// -/// - [JmlParser.ExpressionContext]: [Term] /// - [SeqContext]: [Sequent] /// - [Boolean_literalContext]: [Boolean] /// - [IntegerContext]: [Integer] @@ -31,7 +35,7 @@ /// @author Alexander Weigl /// @version 1 (18.01.25) /// @see de.uka.ilkd.key.nparser.KeYParser.ProofScriptExpressionContext -class ExprEvaluator extends KeYParserBaseVisitor { +class ExprEvaluator { private static final Logger LOGGER = LoggerFactory.getLogger(ExprEvaluator.class); private final EngineState state; @@ -39,78 +43,113 @@ class ExprEvaluator extends KeYParserBaseVisitor { this.state = engineState; } - @Override - public Object visitProofScriptCodeBlock(ProofScriptCodeBlockContext ctx) { - URI uri = KeyAst.ProofScript.getUri(ctx.start); - return KeyAst.ProofScript.asAst(uri, ctx); - } - - @Override - public Object visitBoolean_literal(Boolean_literalContext ctx) { - return Boolean.parseBoolean(ctx.getText()); - } - - @Override - public Object visitChar_literal(KeYParser.Char_literalContext ctx) { - return ctx.getText().charAt(1); // skip "'" - } - - @Override - public Object visitInteger(IntegerContext ctx) { - return Integer.parseInt(ctx.getText()); - } - - @Override - public Object visitFloatLiteral(KeYParser.FloatLiteralContext ctx) { - return Float.parseFloat(ctx.getText()); - } - - @Override - public Object visitDoubleLiteral(DoubleLiteralContext ctx) { - return Double.parseDouble(ctx.getText()); - } - - @Override - public String visitString_literal(String_literalContext ctx) { - return trim(ctx.getText(), '"'); - } - - @Override - public Sequent visitSeq(SeqContext ctx) { + private Object evaluateExpression(ParserRuleContext ctx) { var expressionBuilder = new ExpressionBuilder(state.getProof().getServices(), state.getCurrentNamespaces()); expressionBuilder.setAbbrevMap(state.getAbbreviations()); - var t = (Sequent) ctx.accept(expressionBuilder); + var t = ctx.accept(expressionBuilder); var warnings = expressionBuilder.getBuildingIssues(); warnings.forEach(it -> LOGGER.warn("{}", it)); warnings.clear(); return t; + } + + public void addConvertersToValueInjector(ValueInjector v) { + v.addConverter(String.class, ProofScriptExpressionContext.class, this::convertToString); + v.addConverter(Boolean.class, ProofScriptExpressionContext.class, this::convertToBoolean); + v.addConverter(boolean.class, ProofScriptExpressionContext.class, this::convertToBoolean); + v.addConverter(Integer.class, ProofScriptExpressionContext.class, this::convertToInteger); + v.addConverter(int.class, ProofScriptExpressionContext.class, this::convertToInteger); + v.addConverter(JTerm.class, ProofScriptExpressionContext.class, this::convertToTerm); + v.addConverter(Sequent.class, ProofScriptExpressionContext.class, this::convertToSequent); + v.addConverter(TermWithHoles.class, ProofScriptExpressionContext.class, + ctx -> TermWithHoles.fromProofScriptExpression(state, ctx)); + v.addConverter(SequentWithHoles.class, ProofScriptExpressionContext.class, + ctx -> SequentWithHoles.fromParserContext(state, ctx)); + v.addConverter(ScriptBlock.class, ProofScriptExpressionContext.class, + this::convertToScriptBlock); } - @Override - public Object visitSimple_ident(Simple_identContext ctx) { - return evaluateExpression(ctx); + private ScriptBlock convertToScriptBlock(ProofScriptExpressionContext ctx) + throws ConversionException { + if (ctx.proofScriptCodeBlock() == null) { + throw new ConversionException( + "Need a script block here, not: " + ANTLRUtil.reconstructOriginal(ctx)); + } + URI uri = KeyAst.ProofScript.getUri(ctx.start); + return KeyAst.ProofScript.asAst(uri, ctx.proofScriptCodeBlock()); } - @Override - public Object visitTerm(KeYParser.TermContext ctx) { - return evaluateExpression(ctx); + private JTerm convertToTerm(ProofScriptExpressionContext ctx) throws ConversionException { + try { + if (ctx.string_literal() != null) { + String text = StringUtil.trim(ctx.string_literal().getText(), '"'); + return state.toTerm(text, null); + } else if (ctx.proofScriptCodeBlock() != null) { + throw new ConversionException("A block cannot be used as a term"); + } else { + return (JTerm) evaluateExpression( + (ParserRuleContext) ctx.getChild(ParserRuleContext.class, 0)); + } + } catch (Exception e) { + throw new ConversionException( + "Cannot convert expression to term: " + ANTLRUtil.reconstructOriginal(ctx), e); + } } - private Object evaluateExpression(ParserRuleContext ctx) { - var expressionBuilder = - new ExpressionBuilder(state.getProof().getServices(), state.getCurrentNamespaces()); - expressionBuilder.setAbbrevMap(state.getAbbreviations()); - var t = ctx.accept(expressionBuilder); - var warnings = expressionBuilder.getBuildingIssues(); - warnings.forEach(it -> LOGGER.warn("{}", it)); - warnings.clear(); - return t; + private Sequent convertToSequent(ProofScriptExpressionContext ctx) throws ConversionException { + try { + if (ctx.string_literal() != null) { + String text = StringUtil.trim(ctx.string_literal().getText(), '"'); + return state.toSequent(text); + } else if (ctx.proofScriptCodeBlock() != null) { + throw new ConversionException("A block cannot be used as a sequent"); + } else if (ctx.seq() != null) { + return (Sequent) evaluateExpression(ctx.seq()); + } else { + JTerm term = (JTerm) evaluateExpression((ParserRuleContext) ctx.getChild(0)); + return JavaDLSequentKit + .createSuccSequent(ImmutableList.of(new SequentFormula(term))); + } + } catch (Exception e) { + throw new ConversionException( + "Cannot convert expression to sequent: " + ANTLRUtil.reconstructOriginal(ctx)); + } + } + + private Boolean convertToBoolean(ProofScriptExpressionContext ctx) throws ConversionException { + if (ctx.boolean_literal() != null) { + return Boolean.parseBoolean(ctx.boolean_literal().getText()); + } else if (ctx.string_literal() != null) { + String text = StringUtil.trim(ctx.string_literal().getText(), '"'); + return Boolean.parseBoolean(text); + } else { + throw new ConversionException( + "Cannot convert expression to boolean: " + ANTLRUtil.reconstructOriginal(ctx)); + } + } + + private Integer convertToInteger(ProofScriptExpressionContext proofScriptExpressionContext) + throws ConversionException { + if (proofScriptExpressionContext.integer() != null) { + return Integer.parseInt(proofScriptExpressionContext.integer().getText()); + } else if (proofScriptExpressionContext.string_literal() != null) { + String text = + StringUtil.trim(proofScriptExpressionContext.string_literal().getText(), '"'); + return Integer.parseInt(text); + } else { + throw new ConversionException("Cannot convert expression to integer: " + + ANTLRUtil.reconstructOriginal(proofScriptExpressionContext)); + } } - @Override - protected Object aggregateResult(Object aggregate, Object nextResult) { - return nextResult == null ? aggregate : nextResult; + private String convertToString(ProofScriptExpressionContext ctx) { + if (ctx.string_literal() != null) { + return StringUtil.trim(ctx.string_literal().getText(), '"'); + } else { + return ANTLRUtil.reconstructOriginal(ctx).trim(); + } } + } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/FocusCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/FocusCommand.java index 6f9b1f724a2..e8fd5cc9c81 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/FocusCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/FocusCommand.java @@ -14,19 +14,16 @@ import de.uka.ilkd.key.rule.TacletApp; import de.uka.ilkd.key.rule.inst.SVInstantiations; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.PosInTerm; -import org.key_project.logic.Term; import org.key_project.logic.op.sv.SchemaVariable; import org.key_project.prover.sequent.PosInOccurrence; -import org.key_project.prover.sequent.Sequent; import org.key_project.prover.sequent.SequentFormula; -import org.key_project.util.collection.ImmutableList; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import static de.uka.ilkd.key.logic.equality.RenamingTermProperty.RENAMING_TERM_PROPERTY; /** * The command "focus" allows you to select formulas from the current sequent @@ -48,17 +45,31 @@ public FocusCommand() { super(Parameters.class); } + @Documentation(category = "Fundamental", value = """ + The command "focus" allows you to select formulas from the current sequent + to focus verification on. This means that all other formulas are discarded + (i.e. hidden using `hide_right`, `hide_left`). + + Benefits are: The automation is guided into focussing on a relevant set of + formulas. + + The selected set of sequent formulas can be regarded as an equivalent to a + believed "unsat core" of the sequent. + + #### Examples: + - `focus x > 2 ==> x > 1` only keeps the mentioned to formulas in the current goal + removing all other formulas that could distract the automation. + """) static class Parameters { @Argument - public @MonotonicNonNull Sequent toKeep; + @Documentation("The sequent containing the formulas to keep. It may contain placeholder symbols.") + public @MonotonicNonNull SequentWithHoles toKeep; } @Override public void execute(ScriptCommandAst args) throws ScriptException, InterruptedException { - var s = state().getValueInjector().inject(new Parameters(), args); - - Sequent toKeep = s.toKeep; - hideAll(toKeep); + Parameters s = state().getValueInjector().inject(new Parameters(), args); + hideAll(s.toKeep); } @Override @@ -72,38 +83,19 @@ public String getName() { * @param toKeep sequent containing formulas to keep * @throws ScriptException if no goal is currently open */ - private void hideAll(Sequent toKeep) throws ScriptException { + private void hideAll(SequentWithHoles toKeep) throws ScriptException { Goal goal = state.getFirstOpenAutomaticGoal(); assert goal != null : "not null by contract of the method"; - // The formulas to keep in the antecedent - ImmutableList keepAnte = toKeep.antecedent().asList() - .map(SequentFormula::formula); - ImmutableList ante = - goal.sequent().antecedent().asList(); - - for (SequentFormula seqFormula : ante) { - // This means "!keepAnte.contains(seqFormula.formula)" but with equality mod renaming! - if (!keepAnte.exists( - it -> { - Term formula = seqFormula.formula(); - return RENAMING_TERM_PROPERTY.equalsModThisProperty(it, formula); - })) { + for (SequentFormula seqFormula : goal.sequent().antecedent().asList()) { + if (!toKeep.containsAntecendent(seqFormula)) { Taclet tac = getHideTaclet("left"); makeTacletApp(goal, seqFormula, tac, true); } } - ImmutableList keepSucc = - toKeep.succedent().asList().map(SequentFormula::formula); - ImmutableList succ = - goal.sequent().succedent().asList(); - for (SequentFormula seqFormula : succ) { - if (!keepSucc.exists( - it -> { - Term formula = seqFormula.formula(); - return RENAMING_TERM_PROPERTY.equalsModThisProperty(it, formula); - })) { + for (SequentFormula seqFormula : goal.sequent().succedent().asList()) { + if (!toKeep.containsSuccedent(seqFormula)) { Taclet tac = getHideTaclet("right"); makeTacletApp(goal, seqFormula, tac, false); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/HideCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/HideCommand.java index 6f37e696469..a4aa880fb0f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/HideCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/HideCommand.java @@ -10,6 +10,7 @@ import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.PosInTerm; @@ -25,7 +26,7 @@ import static de.uka.ilkd.key.logic.equality.TermLabelsProperty.TERM_LABELS_PROPERTY; /** - * Proof script command to hide a formula from the sequent. + * Proof script command to hide formulas from the sequent. * * Usage: * @@ -99,9 +100,15 @@ public String getName() { return "hide"; } + @Documentation(category = "Control", + value = """ + The hide command hides all formulas of the current proof goal that are in the given sequent. + The formulas in the given sequent are hidden using the taclets hide_left and hide_right. + """) public static class Parameters { @Argument @MonotonicNonNull + @Documentation("The sequent containing the formulas to hide. Placeholders are allowed.") public Sequent sequent; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/InstantiateCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/InstantiateCommand.java index a4f795b1011..56b841a8e5a 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/InstantiateCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/InstantiateCommand.java @@ -15,6 +15,7 @@ import de.uka.ilkd.key.proof.RuleAppIndex; import de.uka.ilkd.key.rule.PosTacletApp; import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.Flag; import de.uka.ilkd.key.scripts.meta.Option; @@ -30,6 +31,7 @@ import org.key_project.util.collection.ImmutableList; import org.key_project.util.collection.ImmutableSLList; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.Nullable; import static de.uka.ilkd.key.logic.equality.RenamingTermProperty.RENAMING_TERM_PROPERTY; @@ -220,35 +222,43 @@ public String getName() { return "instantiate"; } - @Override - public String getDocumentation() { - return """ - instantiate var=a occ=2 with="a_8" hide -

- instantiate formula="\\forall int a; phi(a)" with="a_8\" - """; - } + @Documentation(category = "Fundamental", + value = """ + Instantiate a universally quantified formula (in the antecedent; + or an existentially quantified formula in succedent) by a term. + One of `var` or `formula` must be specified. If `var` is given, the formula is determined by looking for + a particular occurrence of a quantifier over that variable name. + If `formula` is given, that quantified formula is used directly. + `with` must be specified. - /** - * - */ + #### Examples: + + * `instantiate var:a occ:2 with:a_8 hide` + * `instantiate formula:"\\forall int a; phi(a)" with="a_8"` + """) public static class Parameters { + @Documentation("The toplevel quantified formula to instantiate. Placeholder matching symbols can be used.") @Option(value = "formula") @Nullable public JTerm formula; + @Documentation("The name of the bound variable to instantiate.") @Option(value = "var") @Nullable public String var; + @Documentation("The occurrence number of the quantifier over 'var' in the sequent starting at 1. Default is 1.") @Option(value = "occ") public @Nullable int occ = 1; + @Documentation("If given, the rule used for instantiation is the one that hides the instantiated formula to " + + "prevent it from being used for further automatic proof steps.") @Flag("hide") public boolean hide; + @Documentation("The term to instantiate the bound variable with. Must be given.") @Option(value = "with") - public @Nullable JTerm with; + public @MonotonicNonNull JTerm with; } private static class TacletNameFilter extends TacletFilter { diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/JavascriptCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/JavascriptCommand.java index d3bb13607c9..dc7466c2a67 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/JavascriptCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/JavascriptCommand.java @@ -12,11 +12,30 @@ import de.uka.ilkd.key.pp.AbbrevException; import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.prover.sequent.Sequent; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +/** + * This command allows to execute arbitrary JavaScript code. The code is executed in a context where + * the current selected goal is available as {@code goal} and a function {@code setVar(v,t)} is + * available to set an abbreviation (where {@code v} is the name of the variable including the + * leading {@code @} and {@code t} is either a term or a string that can be parsed as a term). + *

+ * Example: + * + *

+ * javascript {
+ *   var x = goal.getAntecedent().get(0).getFormula();
+ *   setVar("@myVar", x);
+ * }
+ * 
+ * + * This command is powerful but should be used with care, as it can easily lead to unsound proofs if + * used incorrectly. + */ public class JavascriptCommand extends AbstractCommand { private static final String PREAMBLE = """ @@ -52,7 +71,26 @@ public String getName() { return "javascript"; } + @Documentation(category = "Internal", + value = """ + This command allows to execute arbitrary JavaScript code. The code is executed in a context where + the current selected goal is available as `goal` and a function `setVar(v,t)` is + available to set an abbreviation (where `v` is the name of the variable including the + leading `@` and `t` is either a term or a string that can be parsed as a term). + + #### Example: + ``` + javascript { + var x = goal.getAntecedent().get(0).getFormula(); + setVar("@myVar", x); + } + ``` + + This command is powerful but should be used with care, as it can easily lead to unsound proofs if + used incorrectly. + """) public static class Parameters { + @Documentation("The JavaScript code to execute.") @Argument public @MonotonicNonNull String script; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/LeaveCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/LeaveCommand.java index c2c9643ee74..3f0c01eba9b 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/LeaveCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/LeaveCommand.java @@ -5,10 +5,15 @@ import de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Documentation(category = "Control", + value = """ + Leave the current goal as it is. Technically, this + marks the current goal to be 'interactive' that is ignored by script commands or calls to automation.""") public class LeaveCommand extends NoArgumentCommand { private static final Logger LOGGER = LoggerFactory.getLogger(LeaveCommand.class.getName()); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/LetCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/LetCommand.java index 14a4aa1cfe9..0e369bfac1e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/LetCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/LetCommand.java @@ -6,10 +6,10 @@ import java.util.List; import java.util.Map; -import de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.logic.JTerm; -import de.uka.ilkd.key.nparser.KeYParser; import de.uka.ilkd.key.pp.AbbrevMap; +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.OptionalVarargs; import de.uka.ilkd.key.scripts.meta.ProofScriptArgument; import org.jspecify.annotations.NullMarked; @@ -32,42 +32,59 @@ /// * Apr,2025 (weigl): remove {@code force} in favor of {@code letf}. /// * Jan,2025 (weigl): add new parameter {@code force} to override bindings. @NullMarked -public class LetCommand implements ProofScriptCommand { +public class LetCommand extends AbstractCommand { + + @Documentation(category = "Fundamental", + value = """ + The let command lets you introduce entries to the abbreviation table. + + let @abbrev1=term1 ... @abbrev2=term2; + + or + + letf @abbrev1=term1 ... @abbrev2=term2; + + One or more key-value pairs are supported where key starts is @ followed by an identifier and + value is a term. + If letf if used instead of let, the let bindings are overridden otherwise conflicts results into an exception.""") + + + public static class Parameters { + @Documentation("Key-value pairs where key is the name of the abbreviation (starting with @) and value is a term.") + @OptionalVarargs(as = JTerm.class) + public Map namedArgs = Map.of(); + } + + public LetCommand() { + super(Parameters.class); + } + @Override public List getArguments() { return List.of(); } + public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new Parameters(), ast); - @Override - public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, - EngineState stateMap) throws ScriptException, InterruptedException { - - AbbrevMap abbrMap = stateMap.getAbbreviations(); + AbbrevMap abbrMap = state().getAbbreviations(); - boolean force = "letf".equals(args.commandName()); + boolean force = "letf".equals(ast.commandName()); - for (Map.Entry entry : args.namedArgs().entrySet()) { + for (Map.Entry entry : args.namedArgs.entrySet()) { String key = entry.getKey(); - if (key.startsWith("#") || key.equals("force")) { - continue; - } - if (!key.startsWith("@")) { - throw new ScriptException("Unexpected parameter to let, only @var allowed: " + key); + if (key.startsWith("@")) { + // get rid of @ + key = key.substring(1); } - // get rid of @ - key = key.substring(1); - if (abbrMap.containsAbbreviation(key) && !force) { throw new ScriptException(key + " is already fixed in this script"); } + try { - final var termCtx = (KeYParser.ProofScriptExpressionContext) entry.getValue(); - final var value = termCtx.accept(stateMap.getEvaluator()); - final var term = stateMap.getValueInjector().convert(value, JTerm.class); - abbrMap.put(term, key, true); + abbrMap.put(entry.getValue(), key, true); } catch (Exception e) { throw new ScriptException(e); } @@ -80,11 +97,6 @@ public String getName() { return "let"; } - @Override - public String getDocumentation() { - return ""; - } - @Override public List getAliases() { return List.of(getName(), "letf"); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/MacroCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/MacroCommand.java index bd7763f428f..3788587a8a8 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/MacroCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/MacroCommand.java @@ -24,8 +24,14 @@ import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.prover.sequent.Sequent; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.Nullable; +/** + * Command to invoke a user-defined macro (like from UI) + * + * See Parameters for documentation. + */ public class MacroCommand extends AbstractCommand { private static final Map macroMap = loadMacroMap(); @@ -164,10 +170,23 @@ private static String formatTermString(String str) { .replace(" +", " "); } + @Documentation(category = "Fundamental", + value = """ + The MacroCommand invokes one of KeY's macros. The macro must be registered to KeY's services. + + The command takes the name of the macro as first argument, followed by optional + parameters to configure the macro. + + The macro is applied to the first open automatic goal in the proof. + + #### Examples: + * `macro "prop-split"` + * `macro "auto-pilot"` + """) public static class Parameters { @Argument @Documentation("Macro name") - public String macroName; + public @MonotonicNonNull String macroName; @Documentation("Run on formula number \"occ\" parameter") @Option(value = "occ") @@ -179,6 +198,7 @@ public static class Parameters { public @Nullable String matches = null; /** Variable macro parameters */ + @Documentation("Macro parameters, given as varargs with prefix 'arg_'. E.g. arg_param1=value1") @OptionalVarargs(as = String.class, prefix = "arg_") public Map instantiations = new HashMap<>(); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ObtainCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ObtainCommand.java new file mode 100644 index 00000000000..7e4a0b6d3aa --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ObtainCommand.java @@ -0,0 +1,212 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.*; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.op.*; +import de.uka.ilkd.key.proof.*; +import de.uka.ilkd.key.rule.*; +import de.uka.ilkd.key.rule.Taclet; +import de.uka.ilkd.key.scripts.meta.*; + +import org.key_project.logic.Name; +import org.key_project.logic.PosInTerm; +import org.key_project.logic.op.sv.SchemaVariable; +import org.key_project.prover.sequent.FormulaChangeInfo; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SemisequentChangeInfo; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSet; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.jspecify.annotations.Nullable; + +/** + * Command that applies a calculus rule All parameters are passed as strings and converted by the + * command. The parameters are: + *
    + *
  1. #2 = rule name
  2. + *
  3. on= key.core.logic.Term on which the rule should be applied to as String (find part of the + * rule)
  4. + *
  5. formula= toplevel formula in which term appears in
  6. + *
  7. occ = occurrence number
  8. + *
  9. inst_= instantiation
  10. + *
+ */ +public class ObtainCommand extends AbstractCommand { + + private static final Name INTRO_TACLET_NAME = new Name("intro"); + private static final Name ALL_RIGHT_TACLET_NAME = new Name("allRight"); + + public ObtainCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "__obtain"; + } + + @Override + public void execute(ScriptCommandAst ast) + throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new Parameters(), ast); + + var obtainMap = (Map) state().getUserData("jml.obtainVarMap"); + if (obtainMap == null) { + throw new ScriptException( + "No obtain variable map found. This command must be used within a JML proof."); + } + + LocationVariable var = obtainMap.keySet().stream() + .filter(lv -> lv.name().toString().equals(args.var)) + .findAny().orElseThrow( + () -> new ScriptException("No such obtain variable registered: " + args.var)); + + JTerm skolem; + if (args.equals != null) { + skolem = executeEquals(var, args.equals); + } else if (args.suchThat != null) { + skolem = executeSuchThat(var, args.suchThat); + } else if (args.fromGoal) { + skolem = executeFromGoal(var); + } else { + throw new ScriptException( + "Exactly one of 'such_that', 'equals', or 'from_goal' must be given."); + } + + obtainMap.put(var, skolem.op(JFunction.class)); + + } + + private JTerm executeFromGoal(LocationVariable var) throws ScriptException { + Goal goal = state.getFirstOpenAutomaticGoal(); + + // This works under the assumption that the first succedent formula is the "goal" formula. + SequentFormula sequentFormula = identifySequentFormula(goal.node()); + JTerm formula = (JTerm) sequentFormula.formula(); + while (formula.op() instanceof UpdateApplication) { + formula = formula.sub(1); + } + if (formula.op() != Quantifier.ALL) { + throw new ScriptException( + "For 'obtain \\from_goal, the goal formula needs to be a universally quantified formula."); + } + + Services services = state().getProof().getServices(); + Taclet intro = state.getProof().getEnv().getInitConfigForEnvironment() + .lookupActiveTaclet(ALL_RIGHT_TACLET_NAME); + TacletApp app = NoPosTacletApp.createNoPosTacletApp(intro); + + SchemaVariable sk = getSV(app.uninstantiatedVars(), "sk"); + String name = + VariableNameProposer.DEFAULT.getNameProposal(var.name().toString(), services, null); + app = app.createSkolemConstant(name, sk, var.sort(), true, services); + + SchemaVariable b = getSV(app.uninstantiatedVars(), "b"); + app = app.addCheckedInstantiation(b, formula.sub(0), services, true); + + SchemaVariable u = getSV(app.uninstantiatedVars(), "u"); + app = app.addCheckedInstantiation(u, + services.getTermBuilder().var((LogicVariable) formula.boundVars().get(0)), services, + true); + app = app.setPosInOccurrence( + new PosInOccurrence(sequentFormula, PosInTerm.getTopLevel(), false), services); + + goal.apply(app); + return app.instantiations().getInstantiation(sk); + } + + private SequentFormula identifySequentFormula(Node node) { + SemisequentChangeInfo changes = + node.getNodeInfo().getSequentChangeInfo().getSemisequentChangeInfo(false); + ImmutableList added = changes.addedFormulas(); + if (!added.isEmpty()) { + if (added.size() == 1) { + return added.get(0); + } + } else { + ImmutableList modified = changes.modifiedFormulas(); + if (modified.size() == 1) { + return modified.get(0).newFormula(); + } + } + throw new IllegalStateException( + "Multiple or no formulas modified or added in last step, cannot identify sequent formula to skolemize."); + } + + private JTerm executeSuchThat(LocationVariable var, @Nullable JTerm suchThat) { + throw new UnsupportedOperationException("such_that not yet supported in obtain."); + } + + private JTerm executeEquals(LocationVariable var, @Nullable JTerm equals) + throws ScriptException { + Services services = state().getProof().getServices(); + Taclet intro = state.getProof().getEnv().getInitConfigForEnvironment() + .lookupActiveTaclet(INTRO_TACLET_NAME); + TacletApp app = NoPosTacletApp.createNoPosTacletApp(intro); + SchemaVariable sk = getSV(app.uninstantiatedVars(), "sk"); + SchemaVariable t = getSV(app.uninstantiatedVars(), "t"); + String name = + VariableNameProposer.DEFAULT.getNameProposal(var.name().toString(), services, null); + app = app.createSkolemConstant(name, sk, var.sort(), true, services); + app = app.addCheckedInstantiation(t, equals, services, true); + state.getFirstOpenAutomaticGoal().apply(app); + return app.instantiations().getInstantiation(sk); + } + + private static SchemaVariable getSV(ImmutableSet schemaVars, String name) { + for (SchemaVariable schemaVar : schemaVars) { + if (schemaVar.name().toString().equals(name)) { + return schemaVar; + } + } + throw new NoSuchElementException("No schema variable with name " + name); + } + + @Documentation(category = "JML", value = """ + Command that introduces a fresh variable with a given name and sort. + Exactly one of `such_that`, `equals`, or `from_goal` must be given. + + The command should not be called directly, but is used internally by + the JML script support within KeY. + """) + public static class Parameters implements ValueInjector.VerifyableParameters { + @Option(value = "var") + @Documentation("Name of the variable to be instantiated.") + public @MonotonicNonNull String var; + + @Option(value = "such_that") + @Documentation("Condition that is to be established for the fresh variable.") + public @Nullable JTerm suchThat; + + @Option(value = "from_goal") + @Documentation("Top-level formula in which the term appears.") + public @Nullable boolean fromGoal = false; + + @Option(value = "equals") + @Documentation("Represented term for which this is an abbreviation.") + public @Nullable JTerm equals; + + @Override + public void verifyParameters() throws IllegalArgumentException, InjectionException { + int cnt = 0; + if (suchThat != null) + cnt++; + if (equals != null) + cnt++; + if (fromGoal) + cnt++; + if (cnt != 1) { + throw new InjectionException( + "Exactly one of 'such_that', 'equals', or 'from_goal' must be given."); + } + } + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/OneStepSimplifierCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/OneStepSimplifierCommand.java new file mode 100644 index 00000000000..d824366c039 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/OneStepSimplifierCommand.java @@ -0,0 +1,98 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.rule.OneStepSimplifierRuleApp; +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.Flag; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.key_project.logic.PosInTerm; +import org.key_project.prover.sequent.*; +import org.key_project.util.collection.ImmutableList; + +import org.jspecify.annotations.Nullable; + +public class OneStepSimplifierCommand extends AbstractCommand { + + public OneStepSimplifierCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "oss"; + } + + @Override + public void execute(ScriptCommandAst command) throws ScriptException, InterruptedException { + + var arguments = state().getValueInjector().inject(new Parameters(), command); + + final Goal goal = state.getFirstOpenAutomaticGoal(); + + if (Boolean.TRUE.equals(arguments.recentOnly)) { + SequentChangeInfo sci = goal.node().getNodeInfo().getSequentChangeInfo(); + var ante = sci.addedFormulas(true) + .prepend(sci.modifiedFormulas(true).map(FormulaChangeInfo::newFormula)); + applyOSS(ante, goal, true); + + var succ = sci.addedFormulas(false) + .prepend(sci.modifiedFormulas(false).map(FormulaChangeInfo::newFormula)); + applyOSS(succ, goal, false); + return; + } + + if (Boolean.TRUE.equals(arguments.antecedent)) { + applyOSS(goal.sequent().antecedent(), goal, true); + } + + if (Boolean.TRUE.equals(arguments.succedent)) { + applyOSS(goal.sequent().succedent(), goal, false); + } + } + + + private static void applyOSS(Iterable antecedent, Goal goal, boolean inAntec) { + for (SequentFormula sf : antecedent) { + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, + new PosInOccurrence(sf, PosInTerm.getTopLevel(), inAntec)); + for (IBuiltInRuleApp builtin : builtins) { + if (builtin instanceof OneStepSimplifierRuleApp) { + goal.apply(builtin); + } + } + } + } + + + @Documentation(category = "Fundamental", + value = """ + The oss command applies the *one step simplifier* on the current proof goal. + This simplifier applies a set of built-in simplification rules to the formulas in the sequent. + It can be configured to apply the one step simplifier only on the antecedent or succedent. + By default, it is applied on both sides of the sequent. + """) + public static class Parameters { + @Documentation("Application of the one step simplifier can be forbidden on the antecedent side by setting " + + + "this option to false. Default is true.") + @Option(value = "antecedent") + public @Nullable Boolean antecedent = Boolean.TRUE; + + @Documentation("Application of the one step simplifier can be forbidden on the succedent side by setting " + + + "this option to false. Default is true.") + @Option(value = "succedent") + public @Nullable Boolean succedent = Boolean.TRUE; + + @Documentation("Limit the application to the recently added or changed formulas. Deactivates the " + + + "antecedent and succedent options.") + @Flag("recentOnly") + public @Nullable Boolean recentOnly = Boolean.FALSE; + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptCommand.java index d4fb03c6735..a8bf31ab11e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptCommand.java @@ -6,6 +6,7 @@ import java.util.List; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.scripts.meta.ArgumentsLifter; import de.uka.ilkd.key.scripts.meta.ProofScriptArgument; import org.jspecify.annotations.NullMarked; @@ -38,15 +39,19 @@ void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, EngineState stateMap) throws ScriptException, InterruptedException; - /// Returns the name of this proof command. The name should be constant and not be clash with - /// the - /// name of other commands. The name is essential for finding this command within an hashmap. + /// Returns the name of this proof command. The name must be a constant and not be clash with + /// the name of other commands. The name is used to identify the command in a script. The name + /// must be amongst the aliases returned by [#getAliases()]. /// /// @return a non-null, non-empty string /// @see ProofScriptEngine + /// @see #getAliases() String getName(); - /// Announce a list of potential aliases of this command. + /// Announce a list of aliases of this command. + /// + /// Aliases of different commands should be disjoint, otherwise the first command found + /// will be executed. /// /// The command can react differently for each alias. The call name is given to /// [#execute(AbstractUserInterfaceControl,ScriptCommandAst,EngineState)] @@ -55,6 +60,7 @@ void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, /// /// @return an unmodifiable list of alias names under which command can be called, including /// [#getName()] + /// @see #getName() default List getAliases() { return List.of(getName()); } @@ -62,5 +68,12 @@ default List getAliases() { /// A documentation for the commands. /// /// @return a non-null string - String getDocumentation(); + default String getDocumentation() { + return ArgumentsLifter.extractDocumentation(getName(), getClass(), null); + } + + /// A category name for this command. This is used to group commands in the UI or documentation. + default String getCategory() { + return ArgumentsLifter.extractCategory(getClass(), null); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java index 4212e60ec03..753d4ee56de 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java @@ -13,7 +13,6 @@ import java.util.stream.Collectors; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; -import de.uka.ilkd.key.nparser.KeYParser; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.nparser.ParsingFacade; import de.uka.ilkd.key.parser.Location; @@ -21,7 +20,6 @@ import de.uka.ilkd.key.proof.Node; import de.uka.ilkd.key.proof.Proof; -import org.antlr.v4.runtime.RuleContext; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,47 +29,24 @@ * @author Alexander Weigl */ public class ProofScriptEngine { - private static final Map COMMANDS = loadCommands(); private static final Logger LOGGER = LoggerFactory.getLogger(ProofScriptEngine.class); - private final List script; - /** - * The initially selected goal. + * The collection of known script commands. */ - private final @Nullable Goal initiallySelectedGoal; + private static final Map COMMANDS = loadCommands(); /** * The engine state map. */ - private EngineState stateMap; - - private @Nullable Consumer commandMonitor; - - public ProofScriptEngine(Path file) throws IOException { - this(ParsingFacade.parseScript(file), null); - } - - public ProofScriptEngine(KeyAst.ProofScript script) { - this(script, null); - } - - /** - * Instantiates a new proof script engine. - * - * @param script the script - * @param initiallySelectedGoal the initially selected goal - */ - public ProofScriptEngine(KeyAst.ProofScript script, Goal initiallySelectedGoal) { - this(script.asAst(), initiallySelectedGoal); - } + private final EngineState stateMap; - public ProofScriptEngine(List script, Goal initiallySelectedGoal) { - this.initiallySelectedGoal = initiallySelectedGoal; - this.script = script; + public ProofScriptEngine(Proof proof) { + super(); + this.stateMap = new EngineState(proof, this); } - private static Map loadCommands() { + static Map loadCommands() { Map result = new HashMap<>(); var loader = ServiceLoader.load(ProofScriptCommand.class); @@ -84,49 +59,48 @@ private static Map loadCommands() { return result; } - public void execute(AbstractUserInterfaceControl uiControl, Proof proof) - throws IOException, InterruptedException, ScriptException { - stateMap = new EngineState(proof, this); + public void setInitiallySelectedGoal(@Nullable Goal initiallySelectedGoal) { + this.stateMap.setGoal(initiallySelectedGoal); + } - if (initiallySelectedGoal != null) { - stateMap.setGoal(initiallySelectedGoal); - } + public void execute(AbstractUserInterfaceControl uiControl, ScriptBlock block) + throws ScriptException, InterruptedException { + execute(uiControl, block.commands()); + } - if (script.isEmpty()) { // no commands given, no work to do - return; - } - // add the filename (if available) to the statemap. - try { - URI url = script.getFirst().location().fileUri(); - stateMap.setBaseFileName(Paths.get(url)); - } catch (NullPointerException | InvalidPathException ignored) { - // weigl: occurs on windows platforms, due to the fact - // that the URI contains "" from ANTLR4 when read by string - // "<" is illegal on windows - } - - // add the observer (if installed) to the state map - if (commandMonitor != null) { - stateMap.setObserver(commandMonitor); - } + public void execute(AbstractUserInterfaceControl uiControl, Path file) + throws ScriptException, InterruptedException, IOException { + KeyAst.ProofScript script = ParsingFacade.parseScript(file); execute(uiControl, script); } - public void execute(AbstractUserInterfaceControl uiControl, ScriptBlock block) + public void execute(AbstractUserInterfaceControl ui, KeyAst.ProofScript script) throws ScriptException, InterruptedException { - execute(uiControl, block.commands()); + execute(ui, script.asAst()); } public void execute(AbstractUserInterfaceControl uiControl, List commands) throws InterruptedException, ScriptException { - if (script.isEmpty()) { // no commands given, no work to do + if (commands.isEmpty()) { // no commands given, no work to do return; } - Location start = script.getFirst().location(); + Location start = commands.getFirst().location(); Proof proof = stateMap.getProof(); + // add the filename (if available) to the statemap. + try { + if (start != null) { + URI url = start.fileUri(); + stateMap.setBaseFileName(Paths.get(url)); + } + } catch (InvalidPathException ignored) { + // weigl: occurs on windows platforms, due to the fact + // that the URI contains "" from ANTLR4 when read by string + // "<" is illegal on windows + } + int cnt = 0; for (ScriptCommandAst ast : commands) { if (Thread.interrupted()) { @@ -138,14 +112,14 @@ public void execute(AbstractUserInterfaceControl uiControl, List LOGGER.debug("{}", g.sequent())); + LOGGER.debug("Commands: {}", commands.stream() + .map(ScriptCommandAst::asCommandLine) + .collect(Collectors.joining("\n"))); throw new ScriptException( String.format("Error while executing script: %s%n%nCommand: %s%nPosition: %s%n", @@ -188,18 +166,6 @@ public void execute(AbstractUserInterfaceControl uiControl, List monitor) { - this.commandMonitor = monitor; + this.stateMap.setObserver(monitor); } public static ProofScriptCommand getCommand(String commandName) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java index 8c2b4796c40..54f0da1e0cb 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java @@ -4,6 +4,7 @@ package de.uka.ilkd.key.scripts; import java.util.*; +import java.util.stream.Collectors; import de.uka.ilkd.key.java.Services; import de.uka.ilkd.key.logic.*; @@ -14,6 +15,7 @@ import de.uka.ilkd.key.proof.RuleAppIndex; import de.uka.ilkd.key.rule.*; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.Option; import de.uka.ilkd.key.scripts.meta.OptionalVarargs; @@ -24,6 +26,7 @@ import org.key_project.prover.proof.rulefilter.TacletFilter; import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.Taclet; +import org.key_project.prover.rules.instantiation.AssumesFormulaInstantiation; import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.prover.sequent.SequentFormula; import org.key_project.util.collection.ImmutableList; @@ -58,23 +61,6 @@ public String getName() { return "rule"; } - @Override - public String getDocumentation() { - return """ - Command that applies a calculus rule. - All parameters are passed as strings and converted by the command. - - The parameters are: -
    -
  1. #2 = rule name
  2. -
  3. on= key.core.logic.Term on which the rule should be applied to as String (find part of the rule)
  4. -
  5. formula= toplevel formula in which term appears in
  6. -
  7. occ = occurrence number
  8. -
  9. inst_= instantiation
  10. -
- """; - } - @Override public void execute(ScriptCommandAst params) throws ScriptException, InterruptedException { @@ -160,9 +146,11 @@ private TacletApp instantiateTacletApp(final Parameters p, final EngineState sta ImmutableList assumesCandidates = theApp .findIfFormulaInstantiations(state.getFirstOpenAutomaticGoal().sequent(), services); - assumesCandidates = ImmutableList.fromList(filterList(p, assumesCandidates)); + assumesCandidates = ImmutableList.fromList(filterList(services, p, assumesCandidates)); - if (assumesCandidates.size() != 1) { + if (assumesCandidates.size() == 0) { + throw new ScriptException("No \\assumes instantiation"); + } else if (assumesCandidates.size() != 1) { throw new ScriptException("Not a unique \\assumes instantiation"); } @@ -241,7 +229,7 @@ private IBuiltInRuleApp builtInRuleApp(Parameters p, EngineState state, BuiltInR throw new ScriptException("No matching applications."); } - if (p.occ < 0) { + if (p.occ == null || p.occ < 0) { if (matchingApps.size() > 1) { throw new ScriptException("More than one applicable occurrence"); } @@ -249,7 +237,7 @@ private IBuiltInRuleApp builtInRuleApp(Parameters p, EngineState state, BuiltInR return matchingApps.get(0); } else { if (p.occ >= matchingApps.size()) { - throw new ScriptException("Occurence " + p.occ + throw new ScriptException("Occurrence " + p.occ + " has been specified, but there are only " + matchingApps.size() + " hits."); } @@ -260,7 +248,7 @@ private IBuiltInRuleApp builtInRuleApp(Parameters p, EngineState state, BuiltInR private TacletApp findTacletApp(Parameters p, EngineState state) throws ScriptException { ImmutableList allApps = findAllTacletApps(p, state); - List matchingApps = filterList(p, allApps); + List matchingApps = filterList(state.getProof().getServices(), p, allApps); if (matchingApps.isEmpty()) { throw new ScriptException("No matching applications."); @@ -268,7 +256,11 @@ private TacletApp findTacletApp(Parameters p, EngineState state) throws ScriptEx if (p.occ < 0) { if (matchingApps.size() > 1) { - throw new ScriptException("More than one applicable occurrence"); + // todo make a nice string here! + throw new ScriptException("More than one applicable occurrence:\n" + + matchingApps.stream().map( + ap -> ap.posInOccurrence().subTerm() + " " + ap.matchConditions()) + .collect(Collectors.joining("\n"))); } return matchingApps.get(0); } else { @@ -290,7 +282,7 @@ private ImmutableList findBuiltInRuleApps(Parameters p, EngineS ImmutableList allApps = ImmutableSLList.nil(); for (SequentFormula sf : g.node().sequent().antecedent()) { - if (!isFormulaSearchedFor(p, sf, services)) { + if (!isSequentFormulaSearchedFor(p, sf, services)) { continue; } @@ -299,7 +291,7 @@ private ImmutableList findBuiltInRuleApps(Parameters p, EngineS } for (SequentFormula sf : g.node().sequent().succedent()) { - if (!isFormulaSearchedFor(p, sf, services)) { + if (!isSequentFormulaSearchedFor(p, sf, services)) { continue; } @@ -321,7 +313,7 @@ private ImmutableList findAllTacletApps(Parameters p, EngineState sta ImmutableList allApps = ImmutableSLList.nil(); for (SequentFormula sf : g.node().sequent().antecedent()) { - if (!isFormulaSearchedFor(p, sf, services)) { + if (!isSequentFormulaSearchedFor(p, sf, services)) { continue; } @@ -330,7 +322,7 @@ private ImmutableList findAllTacletApps(Parameters p, EngineState sta } for (SequentFormula sf : g.node().sequent().succedent()) { - if (!isFormulaSearchedFor(p, sf, services)) { + if (!isSequentFormulaSearchedFor(p, sf, services)) { continue; } @@ -350,8 +342,7 @@ private ImmutableList findAllTacletApps(Parameters p, EngineState sta * @param sf The {@link SequentFormula} to check. * @return true if sf matches. */ - private boolean isFormulaSearchedFor(Parameters p, - SequentFormula sf, Services services) + private boolean isSequentFormulaSearchedFor(Parameters p, SequentFormula sf, Services services) throws ScriptException { Term term = sf.formula(); final boolean satisfiesFormulaParameter = @@ -380,13 +371,14 @@ private static String formatTermString(String str) { /* * Filter those apps from a list that are according to the parameters. */ - private List filterList(Parameters p, ImmutableList list) { + private List filterList(Services services, Parameters p, + ImmutableList list) { List matchingApps = new ArrayList<>(); + TermComparisonWithHoles matcher = p.on == null ? null : p.on.getMatcher(); for (TacletApp tacletApp : list) { + boolean add = true; if (tacletApp instanceof PosTacletApp pta) { - JTerm term = (JTerm) pta.posInOccurrence().subTerm(); - boolean add = - p.on == null || RENAMING_TERM_PROPERTY.equalsModThisProperty(term, p.on); + add = matcher == null || matcher.matches(pta.posInOccurrence()); for (var entry : pta.instantiations().getInstantiationMap()) { final SchemaVariable sv = entry.key(); @@ -398,6 +390,10 @@ private List filterList(Parameters p, ImmutableList list) || userInst.equalsModProperty(ptaInst, IRRELEVANT_TERM_LABELS_PROPERTY); } + if (tacletApp.assumesFormulaInstantiations() != null) { + add &= checkAssumes(p, tacletApp.assumesFormulaInstantiations(), services); + } + if (add) { matchingApps.add(pta); } @@ -406,27 +402,67 @@ private List filterList(Parameters p, ImmutableList list) return matchingApps; } + private boolean checkAssumes(Parameters p, + ImmutableList ifFormulaInstantiations, Services services) { + if (p.assumes == null) { + // no "assumes" restrictions specified. + return true; + } + + return p.assumes.matches(ifFormulaInstantiations); + } + + + @Documentation(category = "Fundamental", + value = """ + This command can be used to apply a calculus rule to the currently active open goal. + + #### Examples: + - `rule cut inst_cutFormula: (a > 0)` applies the cut rule on the formula `a > 0` like the cut command. + - `rule and_right on=(__ & __)` applies the rule `and_right` to the second occurrence + of a conjunction in the succedent. + - `rule my_rule on=(f(x)) formula="f\\(.*search.*\\)"` applies the rule `my_rule` to the term + `f(x)` in a formula matching the regular expression. + """) public static class Parameters { @Argument + @Documentation("Name of the rule to be applied.") public @MonotonicNonNull String rulename; @Option(value = "on") - public @Nullable JTerm on; + @Documentation("Term on which the rule should be applied to (matching the 'find' clause of the rule). " + + + "This may contain placeholders.") + public @Nullable TermWithHoles on; @Option(value = "formula") + @Documentation("Top-level formula in which the term appears. This may contain placeholders.") public @Nullable JTerm formula; @Option(value = "occ") - public @Nullable int occ = -1; + @Documentation("Occurrence number if more than one occurrence matches. The first occurrence is 1. " + + + "If ommitted, there must be exactly one occurrence.") + public @Nullable Integer occ = -1; /** * Represents a part of a formula (may use Java regular expressions as long as supported by * proof script parser). Rule is applied to the sequent formula which matches that string. */ + @Documentation("Instead of giving the toplevl formula completely, a regular expression can be " + + + "specified to match the toplevel formula.") @Option(value = "matches") public @Nullable String matches = null; + @Option(value = "assumes") + @Documentation(""" + If the rule has an `\\assumes` clause, this can be used to restrict the instantiations + """) + public @Nullable SequentWithHoles assumes; + @OptionalVarargs(as = JTerm.class, prefix = "inst_") + @Documentation("Instantiations for term schema variables used in the rule.") public Map instantiations = new HashMap<>(); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SMTCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SMTCommand.java index b507e5ca40c..b526115a361 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SMTCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SMTCommand.java @@ -7,6 +7,7 @@ import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.Flag; import de.uka.ilkd.key.scripts.meta.Option; import de.uka.ilkd.key.settings.DefaultSMTSettings; @@ -23,6 +24,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Command to invoke an SMT solver on the current goal(s). + * + * See Parameters for documentation. + */ public class SMTCommand extends AbstractCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SMTCommand.class); @@ -108,11 +114,21 @@ private SolverTypeCollection computeSolvers(String value) throws ScriptException return new SolverTypeCollection(value, 1, types); } + @Documentation(category = "Fundamental", value = """ + The smt command invokes an SMT solver on the current goal(s). + By default, it uses the Z3 solver on the first open automatic goal. + If the option 'all' is given, it runs on all open goals. + If the option 'solver' is given, it uses the specified solver(s) instead of Z3. + Multiple solvers can be specified by separating their names with commas. + The available solvers depend on your system: KeY supports at least z3, cvc5. + """) public static class SMTCommandArguments { @Option("solver") public String solver = "Z3"; + @Deprecated @Flag(value = "all") + @Documentation("*Deprecated!* Apply the command on all open goals instead of only the first open automatic goal.") public boolean all = false; @Option(value = "timeout") diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveInstCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveInstCommand.java index 61bf7156e1f..72cf316bd6f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveInstCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveInstCommand.java @@ -8,6 +8,7 @@ import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.pp.AbbrevMap; import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.op.Function; @@ -24,6 +25,12 @@ * * @author Dominic Steinhoefel */ +@Documentation(category = "Internal", + value = """ + Saves the instantiation of a schema variable by the last taclet application into an abbreviation for later use. + A nice use case is a manual loop invariant rule application, where the newly introduced anonymizing Skolem constants can be saved for later interactive instantiations. + As for the let command, it is not allowed to call this command multiple times with the same name argument (all names used for remembering instantiations are "final"). + """) public class SaveInstCommand extends AbstractCommand { public SaveInstCommand() { super(null); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveNewNameCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveNewNameCommand.java index 49d28077f40..a3e233e0351 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveNewNameCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveNewNameCommand.java @@ -12,6 +12,8 @@ import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Node; import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.Option; import org.key_project.logic.Name; @@ -87,9 +89,21 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup } } + @Documentation(category = "Internal", + value = """ + Special "Let" usually to be applied immediately after a manual rule application. Saves a new name + introduced by the last rule which matches certain criteria into an abbreviation for + later use. A nice use case is a manual loop invariant rule application, where the newly + introduced anonymizing Skolem constants can be saved for later interactive instantiations. As for + the let command, it is not allowed to call this command multiple times with the same name + argument (all names used for remembering instantiations are "final"). + """) public static class Parameters { - @Option(value = "#2") + @Documentation("The abbreviation to store the new name under, must start with @") + @Argument public String abbreviation; + + @Documentation("A regular expression to match the new name against, must match exactly one name") @Option(value = "matches") public String matches; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SchemaVarCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SchemaVarCommand.java index eef052c8e0d..a953ff806f7 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SchemaVarCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SchemaVarCommand.java @@ -9,6 +9,7 @@ import de.uka.ilkd.key.logic.op.SchemaVariableFactory; import de.uka.ilkd.key.pp.AbbrevMap; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Name; import org.key_project.logic.sort.Sort; @@ -18,6 +19,7 @@ /** * */ +@Deprecated public class SchemaVarCommand extends AbstractCommand { public SchemaVarCommand() { @@ -65,6 +67,9 @@ public String getName() { return "schemaVar"; } + @Documentation(category = "Internal", value = """ + Defines a schema variable that can be used in subsequent commands. + """) public static class Parameters { @Argument(0) public @MonotonicNonNull String type; diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommand.java index 66d1ec2b656..a8fa2ba7093 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommand.java @@ -8,11 +8,16 @@ import java.nio.file.Path; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Includes and runs another script file. + * See Parameters for more documentation. + */ public class ScriptCommand extends AbstractCommand { private static final Logger LOGGER = LoggerFactory.getLogger(ProofScriptCommand.class); @@ -21,7 +26,9 @@ public ScriptCommand() { super(Parameters.class); } + @Documentation(category = "Control", value = "Includes and runs another script file.") public static class Parameters { + @Documentation("The filename of the script to include. May be relative to the current script.") @Argument public @MonotonicNonNull String filename; } @@ -38,9 +45,9 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc LOGGER.info("Included script {}", file); try { - ProofScriptEngine pse = new ProofScriptEngine(file); + ProofScriptEngine pse = new ProofScriptEngine(proof); pse.setCommandMonitor(state().getObserver()); - pse.execute(uiControl, proof); + pse.execute(uiControl, file); } catch (NoSuchFileException e) { // The message is very cryptic otherwise. throw new ScriptException("Script file '" + file + "' not found", e); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommandAst.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommandAst.java index 69bc8c728ea..8a2de058e0e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommandAst.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommandAst.java @@ -10,6 +10,7 @@ import de.uka.ilkd.key.nparser.KeYParser; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; +import de.uka.ilkd.key.util.ANTLRUtil; import org.antlr.v4.runtime.ParserRuleContext; import org.jspecify.annotations.NullMarked; @@ -73,12 +74,11 @@ public static String asReadableString(Object value) { if (value instanceof ScriptBlock b) { return b.asCommandLine(); } - if (value instanceof KeYParser.ProofScriptCodeBlockContext ctx) { asReadableString(KeyAst.ProofScript.asAst(null, ctx)); } if (value instanceof ParserRuleContext ctx) { - return ctx.getText(); + return ANTLRUtil.reconstructOriginal(ctx); } return Objects.toString(value); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptException.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptException.java index f55b350e369..4fc21d7dcef 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptException.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptException.java @@ -12,7 +12,7 @@ public class ScriptException extends Exception implements HasLocation { private static final long serialVersionUID = -1200219771837971833L; - private final Location location; + private final @Nullable Location location; public ScriptException() { super(); @@ -24,7 +24,7 @@ public ScriptException(String message, Location location, Throwable cause) { this.location = location; } - public ScriptException(String message, Location location) { + public ScriptException(String message, @Nullable Location location) { super(message); this.location = location; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptLineParser.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptLineParser.java index c853a825023..e4f7d40c5f7 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptLineParser.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptLineParser.java @@ -15,9 +15,10 @@ import org.jspecify.annotations.Nullable; /** + * This class was used to parse script lines before the parsing was integrated into the general + * ANTLR parser for KeY files. * * @author mattias ulbrich - * */ class ScriptLineParser { @@ -248,7 +249,8 @@ private void exc(int c) throws ScriptException { } private Location getLocation() { - return new Location(fileURI, Position.newOneBased(line, col)); + Position pos = line >= 1 ? Position.newOneBased(line, col) : Position.UNDEFINED; + return new Location(fileURI, pos); } public int getOffset() { diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SelectCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SelectCommand.java index 650ef75542d..2949f3f2e7d 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SelectCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SelectCommand.java @@ -12,7 +12,10 @@ import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Node; import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.InjectionException; import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.scripts.meta.ValueInjector; import org.key_project.logic.Term; import org.key_project.prover.sequent.Semisequent; @@ -24,12 +27,15 @@ import static de.uka.ilkd.key.logic.equality.RenamingTermProperty.RENAMING_TERM_PROPERTY; +/** + * The SelectCommand selects a goal in the current proof. See documentation of {@link Parameters} + * for more information. + */ public class SelectCommand extends AbstractCommand { public SelectCommand() { super(Parameters.class); } - @Override public void execute(ScriptCommandAst params) throws ScriptException, InterruptedException { var args = state().getValueInjector().inject(new Parameters(), params); @@ -147,19 +153,54 @@ public String getName() { return "select"; } - public static class Parameters { + @Documentation(category = "Control", value = """ + The select command selects a goal in the current proof. + Exactly one of the parameters must be given. + The next command will then continue on the selected goal. + + #### Examples: + - `select formula: (x > 0)` + - `select number: -2` + - `select branch: "Loop Invariant"` + """) + public static class Parameters implements ValueInjector.VerifyableParameters { /** A formula defining the goal to select */ + @Documentation("A formula defining the goal to select. May contain placeholder symbols. If there is a formula " + + + "matching the given formula in multiple goals, the first one is selected.") @Option(value = "formula") public @Nullable JTerm formula; + /** * The number of the goal to select, starts with 0. Negative indices are also allowed: -1 is * the last goal, -2 the second-to-last, etc. */ + @Documentation("The number of the goal to select, starts with 0. Negative indices are also allowed: -1 is " + + + "the last goal, -2 the second-to-last, etc.") @Option(value = "number") public @Nullable Integer number; + /** The name of the branch to select */ + @Documentation("The name of the branch to select. If there are multiple branches with the same name, " + + + "the first one is selected.") @Option(value = "branch") public @Nullable String branch; - } + @Override + public void verifyParameters() throws IllegalArgumentException, InjectionException { + int cnt = 0; + if (formula != null) + cnt++; + if (number != null) + cnt++; + if (branch != null) + cnt++; + if (cnt != 1) { + throw new InjectionException( + "Exactly one of 'formula', 'branch' or 'number' are required"); + } + } + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java new file mode 100644 index 00000000000..07dfe1c6113 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java @@ -0,0 +1,88 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.List; + +import de.uka.ilkd.key.nparser.KeYParser; +import de.uka.ilkd.key.nparser.KeyAst; +import de.uka.ilkd.key.nparser.ParsingFacade; + +import org.key_project.prover.rules.instantiation.AssumesFormulaInstantiation; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.ParserRuleContext; +import org.jspecify.annotations.NullMarked; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NullMarked +public class SequentWithHoles { + + + private final List antecedent; + private final List succedent; + + private SequentWithHoles(List antecedent, List succedent) { + this.antecedent = antecedent; + this.succedent = succedent; + } + + private static final Logger LOGGER = LoggerFactory.getLogger(SequentWithHoles.class); + + public static SequentWithHoles fromString(EngineState engineState, String str) { + KeyAst.Seq seq = ParsingFacade.parseSequent(CharStreams.fromString(str)); + return fromParserContext(engineState, seq.ctx); + } + + public static SequentWithHoles fromParserContext(EngineState state, ParserRuleContext ctx) { + + if (ctx instanceof KeYParser.ProofScriptExpressionContext psctx) { + ctx = psctx.seq(); + } + + if (ctx instanceof KeYParser.SeqContext seqCtx) { + List antecedent = new java.util.ArrayList<>(); + KeYParser.SemisequentContext semseq = seqCtx.ant; + while (semseq != null && semseq.term() != null) { + antecedent.add(TermWithHoles.fromParserContext(state, semseq.term())); + semseq = semseq.semisequent(); + } + + List succedent = new java.util.ArrayList<>(); + semseq = seqCtx.suc; + while (semseq != null && semseq.term() != null) { + succedent.add(TermWithHoles.fromParserContext(state, semseq.term())); + semseq = semseq.semisequent(); + } + return new SequentWithHoles(antecedent, succedent); + } + + throw new IllegalArgumentException("Not a sequent: " + ctx.getText()); + } + + // TODO currently this does not check if the instantiation is on the correct side ... + public boolean matches(ImmutableList ifFormulaInstantiations) { + + for (AssumesFormulaInstantiation assF : ifFormulaInstantiations) { + var form = assF.getSequentFormula(); + if (antecedent.stream().noneMatch(f -> f.matchesToplevel(form)) && + succedent.stream().noneMatch(f -> f.matchesToplevel(form))) { + return false; + } + } + + return true; + } + + public boolean containsAntecendent(SequentFormula seqFormula) { + return antecedent.stream().anyMatch(f -> f.matchesToplevel(seqFormula)); + } + + public boolean containsSuccedent(SequentFormula seqFormula) { + return succedent.stream().anyMatch(f -> f.matchesToplevel(seqFormula)); + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java index c0943975128..addd3beead8 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java @@ -6,11 +6,13 @@ import java.util.HashMap; import java.util.Map; +import java.util.Stack; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.proof.init.Profile; import de.uka.ilkd.key.rule.OneStepSimplifier; +import de.uka.ilkd.key.scripts.meta.Documentation; import de.uka.ilkd.key.scripts.meta.Option; import de.uka.ilkd.key.scripts.meta.OptionalVarargs; import de.uka.ilkd.key.settings.ProofSettings; @@ -18,7 +20,7 @@ import de.uka.ilkd.key.strategy.StrategyFactory; import de.uka.ilkd.key.strategy.StrategyProperties; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; public class SetCommand extends AbstractCommand { public SetCommand() { @@ -29,12 +31,18 @@ public SetCommand() { public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { var args = state.getValueInjector().inject(new Parameters(), arguments); + if (args.settings.isEmpty()) { + throw new IllegalArgumentException( + "You have to set oss, steps, stack, or key(s) and value(s)."); + } + args.settings.remove("oss"); args.settings.remove("steps"); + args.settings.remove("stack"); final Proof proof = state.getProof(); - final StrategyProperties newProps = + StrategyProperties newProps = proof.getSettings().getStrategySettings().getActiveStrategyProperties(); if (args.oneStepSimplification != null) { @@ -44,6 +52,42 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup Strategy.updateStrategySettings(proof, newProps); OneStepSimplifier.refreshOSS(proof); } + + if (args.proofSteps != null) { + state.setMaxAutomaticSteps(args.proofSteps); + } + + if (args.stackAction != null) { + Stack stack = + (Stack) state.getUserData("settingsStack"); + if (stack == null) { + stack = new Stack<>(); + state.putUserData("settingsStack", stack); + } + switch (args.stackAction) { + case "push": + stack.push(newProps.clone()); + break; + case "pop": + // TODO sensible error if empty + var resetProps = stack.pop(); + updateStrategySettings(state, resetProps); + break; + default: + throw new IllegalArgumentException("stack must be either push or pop."); + } + } else if (args.userKey != null) { + String[] kv = args.userKey.split(":", 2); + if (kv.length != 2) { + throw new IllegalArgumentException( + "userData must be of the form key:value. Use userData:\"myKey:myValue\"."); + } + state.putUserData("user." + kv[0], kv[1]); + } else { + throw new IllegalArgumentException( + "You have to set oss, steps, stack, or key(s) and value(s)."); + } + if (args.proofSteps != null) { state.setMaxAutomaticSteps(args.proofSteps); } @@ -68,7 +112,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup * quite complicated implementation, which is inspired by StrategySelectionView. */ - public static void updateStrategySettings(EngineState state, StrategyProperties p) { + protected static void updateStrategySettings(EngineState state, StrategyProperties p) { final Proof proof = state.getProof(); final Strategy strategy = getStrategy(state, p); @@ -104,20 +148,26 @@ public String getName() { } public static class Parameters { - /** - * One Step Simplification parameter - */ + + @Documentation("Enable/disable one-step simplification") @Option(value = "oss") public @Nullable Boolean oneStepSimplification; - /** - * Maximum number of proof steps parameter - */ + @Documentation("Maximum number of proof steps") @Option(value = "steps") public @Nullable Integer proofSteps; - /***/ + @Documentation("key-value pairs to set") @OptionalVarargs public Map settings = HashMap.newHashMap(0); + + @Documentation("Push or pop the current settings to/from a stack of settings (mostly used internally)") + @Option(value = "stack") + public @Nullable String stackAction; + + @Documentation("Set user-defined key-value pair (Syntax: userData:\"key:value\")") + @Option(value = "userData") + public @Nullable String userKey; + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetEchoCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetEchoCommand.java index 843b4038f59..bd71fef7408 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetEchoCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetEchoCommand.java @@ -5,12 +5,17 @@ import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * A simple "echo" command for giving feedback to human observers during lengthy executions. + * An internal command to switch on/off echoing of executed commands. */ +@Deprecated +@Documentation(category = "Internal", value = """ + An internal command to switch on/off echoing of executed commands. + """) public class SetEchoCommand extends AbstractCommand { public SetEchoCommand() { super(Parameters.class); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetFailOnClosedCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetFailOnClosedCommand.java index 34119c7b6de..54c7a48d969 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SetFailOnClosedCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetFailOnClosedCommand.java @@ -5,6 +5,7 @@ import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -15,7 +16,11 @@ * complexity in a try-and-error manner, etc.). * * @author Dominic Steinhoefel + * + * @deprecated This should be merged in the {@link SetCommand} with a parameter like "failonclosed". */ +@Deprecated +@Documentation(category = "Control", value = "") public class SetFailOnClosedCommand extends AbstractCommand { public SetFailOnClosedCommand() { super(Parameters.class); diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/SkipCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SkipCommand.java index 18418fb0729..f3c99991b26 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/SkipCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SkipCommand.java @@ -4,7 +4,9 @@ package de.uka.ilkd.key.scripts; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.scripts.meta.Documentation; +@Documentation(category = "Control", value = "Does exactly nothing.") public class SkipCommand extends NoArgumentCommand { @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, @@ -16,9 +18,4 @@ public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst arg public String getName() { return "skip"; } - - @Override - public String getDocumentation() { - return "Does exactly nothing. Really nothing."; - } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java new file mode 100644 index 00000000000..8f96dfa50b1 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java @@ -0,0 +1,337 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.*; + +import de.uka.ilkd.key.java.NameAbstractionTable; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.equality.RenamingTermProperty; +import de.uka.ilkd.key.logic.op.*; + +import org.key_project.logic.PosInTerm; +import org.key_project.logic.Term; +import org.key_project.logic.op.Operator; +import org.key_project.logic.op.QuantifiableVariable; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.Sequent; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSLList; +import org.key_project.util.collection.Pair; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import static de.uka.ilkd.key.scripts.TermWithHoles.*; + +/** + * A property that can be used for comparisons for terms. + * All term labels are ignored in this equality check. Additionally, holes (represented by the + * SortDependingFunction with name "_" and the Predicate with name "__") are treated as wildcards + * that + * match any subterm. + * + * @author Mattias Ulbrich + */ +@NullMarked +public class TermComparisonWithHoles { + + private final JTerm referenceTerm; + + TermComparisonWithHoles(JTerm referenceTerm) { + this.referenceTerm = Objects.requireNonNull(referenceTerm); + } + + public final boolean matches(PosInOccurrence pio) { + JTerm term = (JTerm) pio.subTerm(); + if (term.equalsModProperty(referenceTerm, RenamingTermProperty.RENAMING_TERM_PROPERTY)) { + return true; + } + + PosInTerm focus = findFocus(referenceTerm); + if (focus == null) { + focus = PosInTerm.getTopLevel(); + } + + List focusPaths = new ArrayList<>(); + expandFocusPaths(focus, pio.posInTerm(), PosInTerm.getTopLevel(), focusPaths); + + for (PosInTerm fpath : focusPaths) { + PosInTerm pit = pio.posInTerm().firstN(pio.depth() - fpath.depth()); + JTerm startTerm = (JTerm) pit.getSubTerm(pio.sequentFormula().formula()); + + boolean result = unifyHelp(referenceTerm, startTerm, + ImmutableSLList.nil(), + ImmutableSLList.nil(), + fpath); + if (result) { + return true; + } + } + + return false; + } + + private void expandFocusPaths(PosInTerm focus, PosInTerm input, PosInTerm base, + List collected) { + if (focus.isTopLevel()) { + // fully matched: + collected.add(base); + return; + } + + if (input.isTopLevel()) { + // input is too shallow + return; + } + + if (focus.getIndex() == (char) -1) { + // ellipsis found, we need to expand + expandFocusPaths(focus.up(), input, base, collected); + expandFocusPaths(focus, input.up(), base.prepend((char) input.getIndex()), collected); + } else { + if (focus.getIndex() == input.getIndex()) { + expandFocusPaths(focus.up(), input.up(), base.prepend((char) input.getIndex()), + collected); + } else { + // mismatch + return; + } + } + } + + public final boolean matchesToplevel(SequentFormula sf) { + // we use antecedent here since it does not matter and is never read ... + return matches(new PosInOccurrence(sf, PosInTerm.getTopLevel(), true)); + } + + private static @Nullable PosInTerm findFocus(Term pattern) { + var op = pattern.op(); + if (op instanceof JFunction) { + if (op.name().equals(TermWithHoles.FOCUS_NAME)) { + return PosInTerm.getTopLevel(); + } + if (op.name().equals(TermWithHoles.ELLIPSIS_NAME)) { + PosInTerm subFocus = findFocus(pattern.sub(0)); + if (subFocus != null) { + return subFocus.prepend((char) -1); + } else { + return null; + } + } + } + for (int i = 0; i < pattern.arity(); i++) { + Term sub = pattern.sub(i); + PosInTerm subFocus = findFocus(sub); + if (subFocus != null) { + return subFocus.prepend((char) i); + } + } + return null; + } + + + /** + * Compares two terms modulo bound renaming + * + * @param t0 the first term -- potentially containing holes + * @param t1 the second term + * @param ownBoundVars variables bound above the current position + * @param cmpBoundVars variables bound above the current position + * @return true is returned iff the terms are equal modulo + * bound renaming + */ + private static boolean unifyHelp(JTerm t0, JTerm t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars, + @Nullable PosInTerm expectedFocus) { + + if (t0 == t1 && ownBoundVars.equals(cmpBoundVars)) { + return true; + } + + Operator op = t0.op(); + if (op instanceof SortDependingFunction sdop) { + if (sdop.getKind().equals(HOLE_SORT_DEP_NAME)) { + return true; + } + } else if (op.name().equals(HOLE_PREDICATE_NAME) || op.name().equals(HOLE_NAME)) { + return true; + } else if (op.name().equals(FOCUS_NAME)) { + if (expectedFocus == null || !expectedFocus.isTopLevel()) { + // focus annotation not at expected position + return false; + } + return unifyHelp(t0.sub(0), t1, ownBoundVars, cmpBoundVars, null); + } else if (op.name().equals(ELLIPSIS_NAME)) { + // return true if it hits one subterm ... + Set> deepAllSubs = new HashSet<>(); + computeSubterms(t1, expectedFocus, deepAllSubs); + var lookfor = t0.sub(0); + return deepAllSubs.stream().anyMatch( + t -> unifyHelp(lookfor, t.first, ownBoundVars, cmpBoundVars, t.second)); + } + + + final Operator op0 = t0.op(); + + if (op0 instanceof QuantifiableVariable) { + return handleQuantifiableVariable(t0, t1, ownBoundVars, + cmpBoundVars); + } + + final Operator op1 = t1.op(); + + if (op0 != op1) { + return false; + } + + // nat = handleJava(t0, t1, nat); + // if (nat == FAILED) { + // return false; + // } + + return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, expectedFocus); + } + + private static void computeSubterms(JTerm t, @Nullable PosInTerm expectedPos, + Set> deepAllSubs) { + deepAllSubs.add(new Pair<>(t, expectedPos)); + for (int i = 0; i < t.arity(); i++) { + computeSubterms(t.sub(i), nextFocusPos(expectedPos, i), deepAllSubs); + } + } + + private static boolean handleQuantifiableVariable(JTerm t0, JTerm t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars) { + if (!((t1.op() instanceof QuantifiableVariable) && compareBoundVariables( + (QuantifiableVariable) t0.op(), (QuantifiableVariable) t1.op(), + ownBoundVars, cmpBoundVars))) { + return false; + } + return true; + } + + /** + * compare two quantifiable variables if they are equal modulo renaming + * + * @param ownVar first QuantifiableVariable to be compared + * @param cmpVar second QuantifiableVariable to be compared + * @param ownBoundVars variables bound above the current position + * @param cmpBoundVars variables bound above the current position + */ + private static boolean compareBoundVariables(QuantifiableVariable ownVar, + QuantifiableVariable cmpVar, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars) { + + final int ownNum = indexOf(ownVar, ownBoundVars); + final int cmpNum = indexOf(cmpVar, cmpBoundVars); + + if (ownNum == -1 && cmpNum == -1) { + // if both variables are not bound the variables have to be the + // same object + return ownVar == cmpVar; + } + + // otherwise the variables have to be bound at the same point (and both + // be bound) + return ownNum == cmpNum; + } + + private static int indexOf(QuantifiableVariable var, + ImmutableList list) { + int res = 0; + while (!list.isEmpty()) { + if (list.head() == var) { + return res; + } + ++res; + list = list.tail(); + } + return -1; + } + + + private static NameAbstractionTable checkNat(NameAbstractionTable nat) { + if (nat == null) { + return new NameAbstractionTable(); + } + return nat; + } + + private static boolean descendRecursively(JTerm t0, JTerm t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars, + PosInTerm expectedFocus) { + + for (int i = 0; i < t0.arity(); i++) { + ImmutableList subOwnBoundVars = ownBoundVars; + ImmutableList subCmpBoundVars = cmpBoundVars; + + if (t0.varsBoundHere(i).size() != t1.varsBoundHere(i).size()) { + return false; + } + for (int j = 0; j < t0.varsBoundHere(i).size(); j++) { + final QuantifiableVariable ownVar = t0.varsBoundHere(i).get(j); + final QuantifiableVariable cmpVar = t1.varsBoundHere(i).get(j); + if (ownVar.sort() != cmpVar.sort()) { + return false; + } + + subOwnBoundVars = subOwnBoundVars.prepend(ownVar); + subCmpBoundVars = subCmpBoundVars.prepend(cmpVar); + } + + PosInTerm nextFocus = nextFocusPos(expectedFocus, i); + + boolean newConstraint = unifyHelp(t0.sub(i), t1.sub(i), + subOwnBoundVars, subCmpBoundVars, nextFocus); + + if (!newConstraint) { + return false; + } + } + + return true; + } + + private static @Nullable PosInTerm nextFocusPos(PosInTerm expectedFocus, int i) { + if (expectedFocus != null && !expectedFocus.isTopLevel() + && expectedFocus.getIndexAt(0) == i) { + // we are on the path to the focus + return expectedFocus.lastN(expectedFocus.depth() - 1); + } else { + return null; + } + } + + public List> findTopLevelMatchesInSequent(Sequent sequent) { + List> matches = new ArrayList<>(); + for (SequentFormula sf : sequent.antecedent()) { + if (matchesToplevel(sf)) { + matches.add(new Pair<>(true, sf)); + } + } + for (SequentFormula sf : sequent.succedent()) { + if (matchesToplevel(sf)) { + matches.add(new Pair<>(false, sf)); + } + } + return matches; + } + + + public @Nullable Pair findUniqueToplevelMatchInSequent( + Sequent sequent) { + List> matches = findTopLevelMatchesInSequent(sequent); + if (matches.size() != 1) { + return null; + } else { + return matches.getFirst(); + } + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.java new file mode 100644 index 00000000000..2222ebebb84 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.java @@ -0,0 +1,135 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.List; +import java.util.Objects; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.ldt.JavaDLTheory; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.NamespaceSet; +import de.uka.ilkd.key.logic.op.JFunction; +import de.uka.ilkd.key.logic.op.SortDependingFunction; +import de.uka.ilkd.key.logic.sort.GenericSort; +import de.uka.ilkd.key.nparser.KeYParser; +import de.uka.ilkd.key.nparser.ParsingFacade; +import de.uka.ilkd.key.nparser.builder.ExpressionBuilder; +import de.uka.ilkd.key.scripts.meta.*; +import de.uka.ilkd.key.util.parsing.BuildingIssue; + +import org.key_project.logic.Name; +import org.key_project.logic.sort.AbstractSort; +import org.key_project.logic.sort.Sort; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableSet; +import org.key_project.util.java.StringUtil; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.jspecify.annotations.NullMarked; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NullMarked +public class TermWithHoles { + + private final JTerm term; + + public TermWithHoles(JTerm term) { + this.term = Objects.requireNonNull(term); + } + + public static final Name HOLE_NAME = new Name("?"); + public static final Name HOLE_PREDICATE_NAME = new Name("?fml"); + public static final Name HOLE_SORT_DEP_NAME = new Name("?"); + public static final Name FOCUS_NAME = new Name("?focus"); + public static final Name ELLIPSIS_NAME = new Name("?find"); + + private static final Logger LOGGER = LoggerFactory.getLogger(TermWithHoles.class); + + public boolean matches(PosInOccurrence posInOccurrence) { + return getMatcher().matches(posInOccurrence); + } + + public boolean matchesToplevel(SequentFormula sf) { + return getMatcher().matchesToplevel(sf); + } + + public TermComparisonWithHoles getMatcher() { + return new TermComparisonWithHoles(term); + } + + private static class NothingSort extends AbstractSort { + private final Services services; + + public NothingSort(Services services) { + super(new Name("Nothing"), true); + this.services = services; + } + + @Override + public ImmutableSet extendsSorts() { + return ImmutableSet.from(services.getNamespaces().sorts().allElements()).remove(this); + } + + @Override + public boolean extendsTrans(Sort s) { + return true; + } + } + + public static TermWithHoles fromString(EngineState engineState, String str) { + KeYParser p = ParsingFacade.createParser(CharStreams.fromString(str)); + p.allowMatchId = true; + KeYParser.TermContext term = p.termEOF().term(); + p.getErrorReporter().throwException(); + return fromParserContext(engineState, term); + } + + public static TermWithHoles fromProofScriptExpression(EngineState engineState, + KeYParser.ProofScriptExpressionContext ctx) throws ConversionException { + if (ctx.string_literal() != null) { + String text = StringUtil.stripQuotes(ctx.string_literal().getText()); + return fromString(engineState, text); + } else if (ctx.proofScriptCodeBlock() != null) { + throw new ConversionException("A block cannot be used as a term"); + } else if (ctx.seq() != null) { + throw new ConversionException("A sequent cannot be used as a term"); + } else { + return fromParserContext(engineState, ctx.getRuleContext(ParserRuleContext.class, 0)); + } + } + + public static TermWithHoles fromParserContext(EngineState state, ParseTree ctx) { + var expressionBuilder = + new ExpressionBuilder(state.getProof().getServices(), enrichState(state)); + expressionBuilder.setAbbrevMap(state.getAbbreviations()); + JTerm t = (JTerm) ctx.accept(expressionBuilder); + List warnings = expressionBuilder.getBuildingIssues(); + warnings.forEach(it -> LOGGER.warn("{}", it)); + warnings.clear(); + return new TermWithHoles(t); + } + + private static NamespaceSet enrichState(EngineState state) { + NamespaceSet ns = state.getProof().getServices().getNamespaces().copy(); + + // Sort Nothing as bottom sort + NothingSort nothing = new NothingSort(state.getProof().getServices()); + ns.sorts().add(nothing); + + ns.functions().addSafely(new JFunction(HOLE_NAME, nothing)); + ns.functions().addSafely(new JFunction(HOLE_PREDICATE_NAME, JavaDLTheory.FORMULA)); + ns.functions().addSafely(new JFunction(FOCUS_NAME, nothing, JavaDLTheory.ANY)); + ns.functions().addSafely(new JFunction(ELLIPSIS_NAME, nothing, JavaDLTheory.ANY)); + GenericSort g = new GenericSort(new Name("G")); + ns.functions().addSafely(SortDependingFunction.createFirstInstance(g, HOLE_SORT_DEP_NAME, g, + new Sort[0], false)); + return ns; + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/UnhideCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/UnhideCommand.java index b7622eb9a78..a8a7eb07aa0 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/UnhideCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/UnhideCommand.java @@ -10,6 +10,7 @@ import de.uka.ilkd.key.proof.RuleAppIndex; import de.uka.ilkd.key.rule.NoPosTacletApp; import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.key_project.logic.Term; import org.key_project.logic.op.sv.SchemaVariable; @@ -21,7 +22,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * Proof script command to insert a formula hidden earlier in the proof. + * Proof script command to insert formulas hidden earlier in the proof. * * Usage: * @@ -83,7 +84,13 @@ public String getName() { return "unhide"; } + @Documentation(category = "Control", + value = """ + The unhide command re-inserts formulas that have been hidden earlier in the proof using the hide command. + It takes a sequent as parameter and re-inserts all formulas in this sequent that have been hidden earlier. + """) public static class Parameters { + @Documentation("The sequent containing the formulas to be re-inserted. Placeholders are allowed.") @Argument public @MonotonicNonNull Sequent sequent; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java new file mode 100644 index 00000000000..119d40e49f2 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java @@ -0,0 +1,144 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.regex.Pattern; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.logic.*; +import de.uka.ilkd.key.logic.op.Quantifier; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.*; +import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.key_project.logic.Name; +import org.key_project.logic.PosInTerm; +import org.key_project.logic.op.Operator; +import org.key_project.logic.op.sv.SchemaVariable; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.Pair; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * witness "\exists int x; phi(x)" as="x_12" + *

+ * witness "\forall int x; phi(x)" as="x_13" + *

+ * witness "\exists int x; phi(x)" as="x_14" cut=true + * + * Possibly with an assertion before to make sure that the formula is on the sequent. + * + * @author mulbrich + */ +public class WitnessCommand extends AbstractCommand { + + private static final Pattern GOOD_NAME = Pattern.compile("[a-zA-Z][a-zA-Z0-9_]*"); + private static final Name ANTEC_TACLET = new Name("exLeft"); + private static final Name SUCC_TACLET = new Name("allRight"); + + public WitnessCommand() { + super(Parameters.class); + } + + @Override + public String getName() { + return "witness"; + } + + @Override + public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedException { + + Parameters params = state().getValueInjector().inject(new Parameters(), ast); + + Goal goal = state.getFirstOpenAutomaticGoal(); + Services services = state.getProof().getServices(); + + TermComparisonWithHoles comp = params.formula.getMatcher(); + + // First component: true for antecedent, false for succedent + Pair match = + comp.findUniqueToplevelMatchInSequent(goal.node().sequent()); + if (match == null) { + throw new ScriptException("Cannot unique match the formula argument"); + } + + Operator op = match.second.formula().op(); + Operator expected = match.first ? Quantifier.EX : Quantifier.ALL; + if (op != expected) { + throw new ScriptException("Expected quantifier " + expected + ", but got " + op); + } + + if (!GOOD_NAME.matcher(params.as).matches()) { + throw new ScriptException("Invalid name: " + params.as); + } + + NamespaceSet nss = services.getNamespaces(); + Name asName = new Name(params.as); + if (nss.functions().lookup(asName) != null) { + throw new ScriptException("Name already used as function or predicate: " + params.as); + } + if (nss.programVariables().lookup(asName) != null) { + throw new ScriptException("Name already used as program variable: " + params.as); + } + + Name tacletName = match.first ? ANTEC_TACLET : SUCC_TACLET; + FindTaclet taclet = (FindTaclet) state.getProof().getEnv().getInitConfigForEnvironment() + .lookupActiveTaclet(tacletName); + PosInOccurrence pio = + new PosInOccurrence(match.second, PosInTerm.getTopLevel(), match.first); + MatchConditions mc = new MatchConditions(); + TacletApp app = PosTacletApp.createPosTacletApp(taclet, mc, pio, services); + Set schemaVars = taclet.collectSchemaVars(); + app = app.addInstantiation(getSV(schemaVars, "u"), + services.getTermBuilder().tf().createTerm(match.second.formula().boundVars().get(0)), + true, services); + app = app.addInstantiation(getSV(schemaVars, "b"), match.second.formula().sub(0), true, + services); + app = app.createSkolemConstant(params.as, getSV(schemaVars, "sk"), + match.second.formula().boundVars().get(0).sort(), true, services); + + Goal g = state.getFirstOpenAutomaticGoal(); + g.apply(app); + } + + private static SchemaVariable getSV(Set schemaVars, String name) { + for (SchemaVariable schemaVar : schemaVars) { + if (schemaVar.name().toString().equals(name)) { + return schemaVar; + } + } + throw new NoSuchElementException("No schema variable with name " + name); + } + + @Documentation(category = "Fundamental", + value = """ + Provides a witness symbol for an existential or universal quantifier. + The given formula must be present on the sequent. Placeholders are allowed. + The command fails if the formula cannot be uniquely matched on the sequent. + The witness symbol `as` must be a valid identifier and not already used as function, predicate, or + program variable name. The new function symbol is created as a Skolem constant. + + #### Example: + + If the sequent contains the formula `\\exists int x; x > 0` in the antecedent then the command + `witness "\\exists int x; x > 0" as="x_12"` will introduce the witness symbol `x_12` for which "x_12 > 0` + holds and is added to the antecedent. + """) + public static class Parameters { + @Documentation("The name of the witness symbol to be created.") + @Option(value = "as") + public @MonotonicNonNull String as; + + @Documentation("The formula containing the quantifier for which a witness should be provided. Placeholders are allowed.") + @Argument + public @MonotonicNonNull TermWithHoles formula; + } + +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Argument.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Argument.java index ef796c2c62e..a3e82ea5856 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Argument.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Argument.java @@ -16,6 +16,6 @@ /// Position of this argument in the positional argument list. /// /// @return a non-null string - /// @see ScriptCommandAst#positionalArgs() + /// @see de.uka.ilkd.key.scripts.ScriptCommandAst#positionalArgs() int value() default 0; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java index cced4bcc96d..c3fbddf4505 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java @@ -5,18 +5,21 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; +import java.util.*; + +import de.uka.ilkd.key.scripts.ProofScriptCommand; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * @author Alexander Weigl * @version 1 (21.04.17) */ public final class ArgumentsLifter { + private static final String OPEN_BRACKET = "\u27e8"; + private static final String CLOSE_BRACKET = "\u27e9"; + private ArgumentsLifter() { } @@ -40,21 +43,22 @@ public static String generateCommandUsage(String commandName, Class parameter var sb = new StringBuilder(commandName); for (var meta : args) { sb.append(' '); - sb.append(meta.isRequired() ? '<' : '['); + if (!meta.isRequired() || meta.isFlag()) + sb.append("["); if (meta.isPositional()) { - sb.append(meta.getName()); + sb.append(OPEN_BRACKET + meta.getType().getSimpleName() + " (" + meta.getName() + + ")" + CLOSE_BRACKET); } if (meta.isOption()) { sb.append(meta.getName()); - sb.append(": "); - sb.append(meta.getField().getType().getName()); + sb.append(":"); + sb.append(OPEN_BRACKET + meta.getField().getType().getSimpleName() + CLOSE_BRACKET); } if (meta.isFlag()) { sb.append(meta.getName()); - sb.append("[: true/false]"); } if (meta.isPositionalVarArgs()) { @@ -65,7 +69,8 @@ public static String generateCommandUsage(String commandName, Class parameter sb.append("%s...".formatted(meta.getName())); } - sb.append(meta.isRequired() ? '>' : ']'); + if (!meta.isRequired() || meta.isFlag()) + sb.append("]"); } @@ -76,48 +81,61 @@ public static String extractDocumentation(String command, Class commandClazz, Class parameterClazz) { StringBuilder sb = new StringBuilder(); + Deprecated dep = commandClazz.getAnnotation(Deprecated.class); + if (dep != null) { + sb.append( + "**Caution! This proof script command is deprecated, and may be removed soon!**\n\n"); + } + Documentation docCommand = commandClazz.getAnnotation(Documentation.class); if (docCommand != null) { sb.append(docCommand.value()); sb.append("\n\n"); } + if (parameterClazz == null) { + return sb.toString(); + } + Documentation docAn = parameterClazz.getAnnotation(Documentation.class); if (docAn != null) { sb.append(docAn.value()); sb.append("\n\n"); } - sb.append("Usage: ").append(generateCommandUsage(command, parameterClazz)) - .append("\n\n"); + sb.append("#### Usage: \n`").append(generateCommandUsage(command, parameterClazz)) + .append("`\n\n"); - final var args = getSortedProofScriptArguments(parameterClazz); + List args = getSortedProofScriptArguments(parameterClazz); - for (var meta : args) { + sb.append("#### Parameters:\n"); + for (ProofScriptArgument meta : args) { sb.append("\n\n"); if (meta.isPositional()) { - sb.append("* Argument %s (%s): %s".formatted( + sb.append("* `%s` *(%s%s positional argument, type %s)*:
%s".formatted( meta.getName(), - meta.getField().getType(), + meta.isRequired() ? "" : "optional ", + ordinalStr(meta.getArgumentPosition() + 1), + meta.getField().getType().getSimpleName(), meta.getDocumentation())); } if (meta.isOption()) { - sb.append("* Option %s (%s): %s".formatted( + sb.append("* `%s` *(%snamed option, type %s)*:
%s".formatted( meta.getName(), - meta.getField().getType(), + meta.isRequired() ? "" : "optional ", + meta.getField().getType().getSimpleName(), meta.getDocumentation())); } if (meta.isFlag()) { - sb.append("* Option %s [%s]: %s".formatted( + sb.append("* `%s` *(flag)*:
%s".formatted( meta.getName(), - meta.getFlag().defValue(), meta.getDocumentation())); } if (meta.isPositionalVarArgs()) { - sb.append("* %s... (%s): %s".formatted( + sb.append("* `%s...` (%s): %s".formatted( meta.getName(), meta.getPositionalVarargs().as(), meta.getPositionalVarargs().startIndex(), @@ -125,10 +143,9 @@ public static String extractDocumentation(String command, Class commandClazz, } if (meta.isOptionalVarArgs()) { - sb.append("* %s: %s... (%s): %s".formatted( - meta.getOptionalVarArgs(), - meta.getName(), - meta.getField().getType(), + sb.append("* `%s...`: *(options prefixed by `%s`, type %s)*:
%s".formatted( + meta.getName(), meta.getOptionalVarArgs().prefix(), + meta.getOptionalVarArgs().as().getSimpleName(), meta.getDocumentation())); } @@ -137,42 +154,74 @@ public static String extractDocumentation(String command, Class commandClazz, return sb.toString(); } - private static @NonNull List getSortedProofScriptArguments( - Class parameterClazz) { - Comparator optional = - Comparator.comparing(ProofScriptArgument::isOption); - Comparator positional = - Comparator.comparing(ProofScriptArgument::isPositional); - Comparator flagal = Comparator.comparing(ProofScriptArgument::isFlag); - Comparator allargsal = - Comparator.comparing(ProofScriptArgument::isPositionalVarArgs); - Comparator byRequired = - Comparator.comparing(ProofScriptArgument::isRequired); - Comparator byName = Comparator.comparing(ProofScriptArgument::getName); - - Comparator byPos = Comparator.comparing(it -> { - if (it.isPositionalVarArgs()) { - it.getPositionalVarargs().startIndex(); - } - if (it.isPositional()) { - it.getArgument().value(); + private static String ordinalStr(int post) { + if (post % 100 >= 11 && post % 100 <= 13) { + return post + "th"; + } + return switch (post % 10) { + case 1 -> post + "st"; + case 2 -> post + "nd"; + case 3 -> post + "rd"; + default -> post + "th"; + }; + } + + public static String extractCategory(Class commandClazz, + @Nullable Class parameterClazz) { + Documentation docCommand = commandClazz.getAnnotation(Documentation.class); + if (docCommand != null && !docCommand.category().isBlank()) { + return docCommand.category(); + } + + if (parameterClazz != null) { + Documentation docAn = parameterClazz.getAnnotation(Documentation.class); + if (docAn != null && !docAn.category().isBlank()) { + return docAn.category(); } + } - return -1; - }); + return "Uncategorized"; + } - var comp = optional - .thenComparing(flagal) - .thenComparing(positional) - .thenComparing(allargsal) - .thenComparing(byRequired) - .thenComparing(byPos) - .thenComparing(byName); + private static @NonNull List getSortedProofScriptArguments( + Class parameterClazz) { + // Comparator optional = + // Comparator.comparing(ProofScriptArgument::isOption); + // Comparator positional = + // Comparator.comparing(ProofScriptArgument::isPositional); + // Comparator flagal = + // Comparator.comparing(ProofScriptArgument::isFlag); + // Comparator allargsal = + // Comparator.comparing(ProofScriptArgument::isPositionalVarArgs); + // Comparator byRequired = + // Comparator.comparing(ProofScriptArgument::isRequired); + // Comparator byName = + // Comparator.comparing(ProofScriptArgument::getName); + // + // Comparator byPos = Comparator.comparing(it -> { + // if (it.isPositionalVarArgs()) { + // it.getPositionalVarargs().startIndex(); + // } + // if (it.isPositional()) { + // it.getArgument().value(); + // } + // + // return -1; + // }); + // + // + // var comp = optional + // .thenComparing(flagal) + // .thenComparing(positional) + // .thenComparing(allargsal) + // .thenComparing(byRequired) + // .thenComparing(byPos) + // .thenComparing(byName); var args = Arrays.stream(parameterClazz.getDeclaredFields()) .map(ProofScriptArgument::new) - .sorted(comp) + .sorted(Comparator.comparing(ProofScriptArgument::orderString)) .toList(); return args; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Converter.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Converter.java index ec20894856f..209bd050b01 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Converter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Converter.java @@ -6,16 +6,17 @@ /** * A {@link Converter} translates an instance of {@code R} to an instance of {@code T}. * - * @param + * @param the result type + * @param the source type * @author Alexander Weigl */ public interface Converter { /** - * Translates the textual representation given in {@code s} to an instance of {@code T}. + * Translates one representation given in {@code s} to an instance of {@code R}. * - * @param s a non-null string - * @return an corresponding instance of T - * @throws Exception if there is an error during the translation (format incorrent etc..) + * @param s a non-null argument to convert + * @return a corresponding instance of T after conversion + * @throws Exception if there is an error during the translation (format incorrect etc ...) */ R convert(T s) throws Exception; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Documentation.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Documentation.java index a4fe62a1766..34175e6e723 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Documentation.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Documentation.java @@ -17,4 +17,6 @@ public @interface Documentation { /// @return a non-null string String value(); + + String category() default ""; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/InjectionException.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/InjectionException.java index 41b3735fe3e..d81dab26722 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/InjectionException.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/InjectionException.java @@ -30,4 +30,13 @@ public InjectionException(String message) { public InjectionException(String message, Throwable cause) { super(message, cause); } + + /** + * An injection exception with a cause to be displayed. + * + * @param cause the cause of the exception. + */ + public InjectionException(Throwable cause) { + super(cause); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ProofScriptArgument.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ProofScriptArgument.java index 66939fadcfc..d425df5d34e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ProofScriptArgument.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ProofScriptArgument.java @@ -138,4 +138,18 @@ public boolean hasNoAnnotation() { public Class getType() { return field.getType(); } + + /** + * This is used for ordering arguments in the usage string and documention. + * + * A twodigit number is used for positional arguments. 'M' for mandatory, 'O' for optional flags + * and options. + */ + public String orderString() { + if (isPositional()) + return "%02d".formatted(getArgumentPosition()); + if (isRequired()) + return "M " + getName(); + return "O " + getName(); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/UnknownArgumentException.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/UnknownArgumentException.java new file mode 100644 index 00000000000..3cbd1035958 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/UnknownArgumentException.java @@ -0,0 +1,21 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts.meta; + +/** + * Signals if an unknown/unexpected argument has been provided for injection. + * + * @author Mattias Ulbrich + */ +public class UnknownArgumentException extends InjectionException { + + /** + * An argument required exception with no cause (to display). + * + * @param message the respective String message to be passed. + */ + public UnknownArgumentException(String message) { + super(message); + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java index 4d66ae758a2..eddb19623b8 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java @@ -9,9 +9,10 @@ import de.uka.ilkd.key.scripts.ProofScriptCommand; import de.uka.ilkd.key.scripts.ScriptCommandAst; +import org.key_project.util.java.IntegerUtil; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; /** * @author Alexander Weigl @@ -46,6 +47,10 @@ private record ConverterKey( Class source, Class target) { } + public interface VerifyableParameters { + void verifyParameters() throws IllegalArgumentException, InjectionException; + } + /** * Injects the given {@code arguments} in the {@code obj}. For more details see * {@link #inject(Object, ScriptCommandAst)} @@ -61,8 +66,7 @@ private record ConverterKey( * @throws ConversionException an converter could not translate the given value in arguments */ public static T injection(ProofScriptCommand command, @NonNull T obj, - ScriptCommandAst arguments) throws ArgumentRequiredException, - InjectionReflectionException, NoSpecifiedConverterException, ConversionException { + ScriptCommandAst arguments) throws InjectionException { return getInstance().inject(obj, arguments); } @@ -122,12 +126,41 @@ public static ValueInjector createDefault() { * @see Flag */ public T inject(T obj, ScriptCommandAst arguments) - throws ConversionException, InjectionReflectionException, NoSpecifiedConverterException, - ArgumentRequiredException { + throws InjectionException { List meta = ArgumentsLifter.inferScriptArguments(obj.getClass()); + Set handledOptions = new HashSet<>(); for (ProofScriptArgument arg : meta) { - injectIntoField(arg, arguments, obj); + handledOptions.addAll(injectIntoField(arg, arguments, obj)); + } + + Optional unhandled = arguments.namedArgs().keySet().stream() + .filter(it -> !handledOptions.contains(it)) + .findAny(); + if (unhandled.isPresent()) { + throw new UnknownArgumentException(String.format( + "Unknown option %s (with value %s) was provided. For command: '%s'", + unhandled.get(), + arguments.namedArgs().get(unhandled.get()), + arguments.commandName())); + } + + Optional unhandledPos = IntegerUtil.indexRangeOf(arguments.positionalArgs()) + .stream() + .filter(it -> !handledOptions.contains(it)) + .findAny(); + if (unhandledPos.isPresent()) { + throw new UnknownArgumentException(String.format( + "Unexpected positional argument or flag provided: %s", + arguments.positionalArgs().get(unhandledPos.get()))); + } + + if (obj instanceof VerifyableParameters vp) { + try { + vp.verifyParameters(); + } catch (IllegalArgumentException e) { + throw new InjectionException(e); + } } return obj; @@ -149,19 +182,22 @@ private Map getStringMap(Object obj, ProofScriptArgument vararg) } } - private void injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Object obj) + private List injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Object obj) throws InjectionReflectionException, ArgumentRequiredException, ConversionException, NoSpecifiedConverterException { Object val = null; + List handled = List.of(); if (meta.isPositional()) { final var idx = meta.getArgumentPosition(); if (idx < args.positionalArgs().size()) { val = args.positionalArgs().get(idx); + handled = List.of(idx); } } if (meta.isPositionalVarArgs()) { val = args.positionalArgs(); + handled = IntegerUtil.indexRangeOf(args.positionalArgs()); } if (meta.isOptionalVarArgs()) { @@ -175,28 +211,30 @@ private void injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Ob } } val = result; + handled = new ArrayList<>(result.keySet()); } if (meta.isOption()) { val = args.namedArgs().get(meta.getName()); + handled = List.of(meta.getName()); } if (meta.isFlag()) { val = args.namedArgs().get(meta.getName()); - System.out.println("X" + val + " " + args.namedArgs() + " " + meta.getName()); if (val == null) { // can also be given w/o colon or equal sign, e.g., "command hide;" - var stringStream = args.positionalArgs().stream() - .map(it -> { - try { - return convert(it, String.class); - } catch (NoSpecifiedConverterException | ConversionException e) { - return ""; - } - }); - // val == true iff the name of the flag appear as a positional argument. - val = stringStream.anyMatch(it -> Objects.equals(it, meta.getName())); - System.out.println(val); + int argNo = 0; + for (Object arg : args.positionalArgs()) { + String s = convert(arg, String.class); + if (s.equals(meta.getName())) { + val = Boolean.TRUE; + handled = List.of(argNo); + break; + } + argNo++; + } + } else { + handled = List.of(meta.getName()); } } @@ -206,7 +244,7 @@ private void injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Ob if (meta.isRequired() && meta.getField().get(obj) == null) { throw new ArgumentRequiredException(String.format( "Argument %s (of type %s) is required, but %s was given. For command class: '%s'", - meta.getName(), meta.getField().getType(), null, + meta.getName(), meta.getField().getType().getSimpleName(), null, meta.getField().getDeclaringClass())); } } else { @@ -216,7 +254,7 @@ private void injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Ob } catch (IllegalAccessException e) { throw new InjectionReflectionException("Could not inject values via reflection", e); } - + return handled; } private Object convert(ProofScriptArgument meta, Object val) @@ -249,16 +287,13 @@ private Object convert(ProofScriptArgument meta, Object val) public T convert(Class targetType, Object val) throws NoSpecifiedConverterException, ConversionException { var converter = (Converter) getConverter(targetType, val.getClass()); - if (converter == null) { - throw new NoSpecifiedConverterException( - "No converter registered for class: " + targetType + " from " + val.getClass()); - } try { return (T) converter.convert(val); } catch (Exception e) { throw new ConversionException( String.format("Could not convert value '%s' from type '%s' to type '%s'", - val, val.getClass(), targetType), + ScriptCommandAst.asReadableString(val), + val.getClass().getSimpleName(), targetType.getSimpleName()), e); } } @@ -268,10 +303,6 @@ public T convert(Object val, Class type) @SuppressWarnings("unchecked") var converter = (Converter) getConverter(type, val.getClass()); - if (converter == null) { - throw new NoSpecifiedConverterException( - "No converter registered for class: " + type + " from " + val.getClass(), null); - } try { return converter.convert(val); } catch (Exception e) { @@ -301,17 +332,25 @@ public void addConverter(Converter conv) { /** * Finds a converter for the given class. * - * @param an arbitrary type - * @param ret a non-null class - * @param arg - * @return null or a suitable converter (registered) converter for the requested class. + * @param the result type + * @param the source type + * @param ret the result type class + * @param arg the source type class + * @return a suitable converter (registered) converter for the requested class. null if no such + * converter is known. */ @SuppressWarnings("unchecked") - public @Nullable Converter getConverter(Class ret, Class arg) { - if (ret == arg) { + public @NonNull Converter getConverter(Class ret, Class arg) + throws NoSpecifiedConverterException { + if (ret.isAssignableFrom(arg)) { return (T it) -> (R) it; } - return (Converter) converters.get(new ConverterKey<>(ret, arg)); + Converter result = (Converter) converters.get(new ConverterKey<>(ret, arg)); + if (result == null) { + throw new NoSpecifiedConverterException( + "No converter registered for class: " + ret.getName() + " from " + arg.getName()); + } + return result; } @Override diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java index 849b3f45190..a1856d07fbc 100755 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java @@ -8,18 +8,28 @@ import org.key_project.util.collection.ImmutableSLList; import org.antlr.v4.runtime.RuleContext; +import org.jspecify.annotations.Nullable; /** * A JML assert/assume statement. */ public class TextualJMLAssertStatement extends TextualJMLConstruct { private final KeyAst.Expression context; + private final String optLabel; + private final KeyAst.@Nullable JMLProofScript assertionProof; private final Kind kind; public TextualJMLAssertStatement(Kind kind, KeyAst.Expression clause) { + this(kind, clause, null, null); + } + + public TextualJMLAssertStatement(Kind kind, KeyAst.Expression clause, + KeyAst.@Nullable JMLProofScript assertionProof, String optLabel) { super(ImmutableSLList.nil(), kind.toString() + " " + clause); this.kind = kind; this.context = clause; + this.assertionProof = assertionProof; + this.optLabel = optLabel; } public KeyAst.Expression getContext() { @@ -63,6 +73,10 @@ public Kind getKind() { return kind; } + public String getOptLabel() { + return optLabel; + } + public enum Kind { ASSERT("assert"), ASSUME("assume"); @@ -77,4 +91,8 @@ public String toString() { return name; } } + + public KeyAst.@Nullable JMLProofScript getAssertionProof() { + return assertionProof; + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/JMLSpecFactory.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/JMLSpecFactory.java index 4bad9d0db32..b4178cdae02 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/JMLSpecFactory.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/JMLSpecFactory.java @@ -1505,7 +1505,7 @@ private ProgramVariableCollection createProgramVariablesForStatement(Statement s * @param pm the enclosing method */ public void translateJmlAssertCondition(final JmlAssert jmlAssert, final IProgramMethod pm) { - final var pv = createProgramVariablesForStatement(jmlAssert, pm); + final ProgramVariableCollection pv = createProgramVariablesForStatement(jmlAssert, pm); var io = new JmlIO(services).context(Context.inMethod(pm, tb)) .selfVar(pv.selfVar) .parameters(pv.paramVars) @@ -1513,10 +1513,12 @@ public void translateJmlAssertCondition(final JmlAssert jmlAssert, final IProgra .exceptionVariable(pv.excVar) .atPres(pv.atPres) .atBefore(pv.atBefores); - JTerm expr = io.translateTerm(jmlAssert.getCondition()); + ImmutableList varsInProof = jmlAssert.collectVariablesInProof(io); + io.parameters(pv.paramVars.prepend(varsInProof)); + ImmutableList terms = jmlAssert.collectTerms().map(io::translateTerm); services.getSpecificationRepository().addStatementSpec( jmlAssert, - new SpecificationRepository.JmlStatementSpec(pv, ImmutableList.of(expr))); + new SpecificationRepository.JmlStatementSpec(pv, terms)); } public @Nullable String checkSetStatementAssignee(JTerm assignee) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/ProgramVariableCollection.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/ProgramVariableCollection.java index a431c2cef22..1c0ee8f2073 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/ProgramVariableCollection.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/ProgramVariableCollection.java @@ -79,7 +79,7 @@ public ProgramVariableCollection(LocationVariable selfVar, * * @param selfVar {@code self} * @param paramVars the list of method parameters if the textual specification case is a method - * contract. + * contract. May also contain the local variables visible at a statement. * @param resultVar {@code result} * @param excVar {@code exception} * @param atPreVars a map from every variable {@code var} to {@code \old(var)}. diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlFacade.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlFacade.java index 80b93be0ce9..35fba582253 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlFacade.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlFacade.java @@ -3,16 +3,14 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.speclang.njml; +import java.io.IOException; import java.net.URI; import de.uka.ilkd.key.java.Position; import de.uka.ilkd.key.speclang.PositionedString; import de.uka.ilkd.key.util.parsing.SyntaxErrorReporter; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.*; import org.jspecify.annotations.NonNull; /** @@ -114,4 +112,17 @@ private static ParserRuleContext getExpressionContext(JmlLexer lexer) { p.getErrorReporter().throwException(); return ctx; } + + // FIXME Make sure this is removed. For testing only! + public static void main(String[] args) throws IOException { + String input = new String(System.in.readAllBytes()); + JmlLexer lexer = createLexer(input); + for (Token t : lexer.getAllTokens()) { + System.out.println(t.getText() + " " + t); + } + lexer = createLexer(input); + var parser = createParser(lexer); + var tree = parser.methodlevel_comment(); + System.out.println(tree.toStringTree(parser)); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlIO.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlIO.java index 7fce7c039aa..a886442cea0 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlIO.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlIO.java @@ -8,6 +8,7 @@ import de.uka.ilkd.key.java.Label; import de.uka.ilkd.key.java.Services; import de.uka.ilkd.key.java.abstraction.KeYJavaType; +import de.uka.ilkd.key.java.abstraction.Type; import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.label.OriginTermLabel; import de.uka.ilkd.key.logic.op.IObserverFunction; @@ -20,6 +21,7 @@ import de.uka.ilkd.key.util.InfFlowSpec; import de.uka.ilkd.key.util.mergerule.MergeParamsSpec; +import org.key_project.logic.sort.Sort; import org.key_project.util.collection.ImmutableList; import org.key_project.util.collection.ImmutableSLList; import org.key_project.util.collection.Pair; @@ -199,6 +201,22 @@ public JTerm translateTerm(ParserRuleContext expr) { } } + /** + * Interpret the given parse tree as an KeYJavaType in the current context. + * May return null if the KJT cannot be resolved. + */ + public @Nullable KeYJavaType translateType(JmlParser.TypespecContext ctx) { + Object interpreted = interpret(ctx); + return switch (interpreted) { + case SLExpression slExpression -> slExpression.getType(); + case Sort sort -> services.getJavaInfo().getKeYJavaType(sort); + case KeYJavaType kjt -> kjt; + case Type type -> services.getJavaInfo().getKeYJavaType(type); + default -> throw new IllegalArgumentException("Cannot translate to KeYJavaType: " + + interpreted + " of class " + interpreted.getClass()); + }; + } + /** * Interpret the given parse tree as an JML expression in the current context. Label is * attached. @@ -411,6 +429,4 @@ public void clearWarnings() { warnings = ImmutableSLList.nil(); } - // region - // endregion } diff --git a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java index fa08e4cb385..2f57bcbb133 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java +++ b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java @@ -520,8 +520,11 @@ public Object visitAssume_statement(JmlParser.Assume_statementContext ctx) { @Override public Object visitAssert_statement(JmlParser.Assert_statementContext ctx) { - TextualJMLAssertStatement b = new TextualJMLAssertStatement( - TextualJMLAssertStatement.Kind.ASSERT, new KeyAst.Expression(ctx.expression())); + TextualJMLAssertStatement b = + new TextualJMLAssertStatement(TextualJMLAssertStatement.Kind.ASSERT, + new KeyAst.Expression(ctx.expression()), + KeyAst.JMLProofScript.fromContext(ctx.assertionProof()), + ctx.label == null ? null : ctx.label.getText()); constructs = constructs.append(b); return null; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StrategyProperties.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StrategyProperties.java index c649de42ab2..a43cde3f0cd 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StrategyProperties.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StrategyProperties.java @@ -436,9 +436,8 @@ public void write(Properties p) { } } - @Override - public synchronized Object clone() { + public synchronized StrategyProperties clone() { final Properties p = (Properties) super.clone(); final StrategyProperties sp = new StrategyProperties(); sp.putAll(p); diff --git a/key.core/src/main/java/de/uka/ilkd/key/util/ANTLRUtil.java b/key.core/src/main/java/de/uka/ilkd/key/util/ANTLRUtil.java new file mode 100644 index 00000000000..20e594c92bf --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/util/ANTLRUtil.java @@ -0,0 +1,105 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.util; + +import java.util.ArrayList; +import java.util.List; + +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.NullMarked; + +/** + * Utility to reconstruct the original source text from a given ANTLR4 parse tree. + * + * It is not 100% accurate, but should be good enough for most use cases. + * + * @author Mattias Ulbrich, or rather ChatGPT 5 mini + */ +@NullMarked +public final class ANTLRUtil { + private ANTLRUtil() {} + + /** + * Reconstructs the original source text from a given ANTLR4 parse tree. + */ + public static String reconstructOriginal(ParseTree tree) { + List terminals = new ArrayList<>(); + collectTerminals(tree, terminals); + + StringBuilder sb = new StringBuilder(); + int curLine = -1; + int curCol = 0; + + for (TerminalNode tn : terminals) { + Token t = tn.getSymbol(); + if (t == null) + continue; + if (t.getType() == Token.EOF) + continue; + + if (curLine == -1) { + // use first token to initialize line and column + curCol = t.getCharPositionInLine(); + curLine = t.getLine(); + } + + int line = t.getLine(); + int col = t.getCharPositionInLine(); + + // add newlines to reach the desired line + while (curLine < line) { + sb.append('\n'); + curLine++; + curCol = 0; + } + + // add spaces to reach the desired column + int pad = col - curCol; + if (pad > 0) { + sb.append(" ".repeat(pad)); + curCol = col; + } + + String text = t.getText(); + if (text == null) + text = ""; + + sb.append(text); + + // update current position + int newlines = countOccurrences(text, '\n'); + if (newlines > 0) { + curLine += newlines; + int lastNl = text.lastIndexOf('\n'); + curCol = text.length() - lastNl - 1; + } else { + curCol += text.length(); + } + } + + return sb.toString(); + } + + private static void collectTerminals(ParseTree node, List out) { + if (node == null) + return; + if (node instanceof TerminalNode) { + out.add((TerminalNode) node); + return; + } + for (int i = 0; i < node.getChildCount(); i++) { + collectTerminals(node.getChild(i), out); + } + } + + private static int countOccurrences(String s, char c) { + int cnt = 0; + for (int i = 0; i < s.length(); i++) + if (s.charAt(i) == c) + cnt++; + return cnt; + } +} diff --git a/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.macros.ProofMacro b/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.macros.ProofMacro index d8125e5c21f..cc3c7df0612 100644 --- a/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.macros.ProofMacro +++ b/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.macros.ProofMacro @@ -19,6 +19,7 @@ de.uka.ilkd.key.macros.PropositionalExpansionMacro # de.uka.ilkd.key.macros.PropositionalExpansionWithSimplificationMacro de.uka.ilkd.key.macros.FullPropositionalExpansionMacro de.uka.ilkd.key.macros.TryCloseMacro +de.uka.ilkd.key.macros.TryCloseSideBranchesMacro de.uka.ilkd.key.macros.FinishSymbolicExecutionMacro de.uka.ilkd.key.macros.AutoMacro #de.uka.ilkd.key.macros.FinishSymbolicExecutionUntilJoinPointMacro @@ -30,3 +31,6 @@ de.uka.ilkd.key.macros.OneStepProofMacro de.uka.ilkd.key.macros.WellDefinednessMacro de.uka.ilkd.key.macros.UpdateSimplificationMacro de.uka.ilkd.key.macros.TranscendentalFloatSMTMacro +de.uka.ilkd.key.macros.ScriptAwareMacro +de.uka.ilkd.key.macros.ScriptAwarePrepMacro +de.uka.ilkd.key.macros.SymbolicExecutionOnlyMacro diff --git a/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.scripts.ProofScriptCommand b/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.scripts.ProofScriptCommand index ad6e048bebc..1324c9d9224 100644 --- a/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.scripts.ProofScriptCommand +++ b/key.core/src/main/resources/META-INF/services/de.uka.ilkd.key.scripts.ProofScriptCommand @@ -7,6 +7,8 @@ de.uka.ilkd.key.scripts.MacroCommand de.uka.ilkd.key.scripts.FocusCommand de.uka.ilkd.key.scripts.AutoCommand de.uka.ilkd.key.scripts.CutCommand +de.uka.ilkd.key.scripts.ObtainCommand +# de.uka.ilkd.key.scripts.AssertCommand # it is an alias for CutCommand now de.uka.ilkd.key.scripts.SetCommand de.uka.ilkd.key.scripts.SetEchoCommand de.uka.ilkd.key.scripts.SetFailOnClosedCommand @@ -17,6 +19,7 @@ de.uka.ilkd.key.scripts.LeaveCommand de.uka.ilkd.key.scripts.TryCloseCommand de.uka.ilkd.key.scripts.ExitCommand de.uka.ilkd.key.scripts.InstantiateCommand +de.uka.ilkd.key.scripts.WitnessCommand de.uka.ilkd.key.scripts.SelectCommand de.uka.ilkd.key.scripts.ScriptCommand de.uka.ilkd.key.scripts.LetCommand @@ -25,11 +28,16 @@ de.uka.ilkd.key.scripts.SaveNewNameCommand de.uka.ilkd.key.scripts.SchemaVarCommand de.uka.ilkd.key.scripts.JavascriptCommand de.uka.ilkd.key.scripts.SkipCommand +de.uka.ilkd.key.scripts.OneStepSimplifierCommand +de.uka.ilkd.key.scripts.DependencyContractCommand de.uka.ilkd.key.scripts.AxiomCommand de.uka.ilkd.key.scripts.AssumeCommand # does not exist? # de.uka.ilkd.key.macros.scripts.SettingsCommand -de.uka.ilkd.key.scripts.AssertCommand +de.uka.ilkd.key.scripts.ExpandDefCommand +de.uka.ilkd.key.scripts.AssertOpenGoalsCommand de.uka.ilkd.key.scripts.RewriteCommand de.uka.ilkd.key.scripts.AllCommand de.uka.ilkd.key.scripts.HideCommand de.uka.ilkd.key.scripts.UnhideCommand +de.uka.ilkd.key.scripts.BranchesCommand +de.uka.ilkd.key.scripts.CheatCommand \ No newline at end of file diff --git a/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRuleSets.txt b/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRuleSets.txt new file mode 100644 index 00000000000..3288342e4c1 --- /dev/null +++ b/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRuleSets.txt @@ -0,0 +1,8 @@ +rulesets +alpha +simplify_prog_subset +simplify_prog +simplify_autoname +executeIntegerAssignment +simplify_expression +loop_scope_inv_taclet diff --git a/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRules.txt b/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRules.txt new file mode 100644 index 00000000000..2acdd87a049 --- /dev/null +++ b/key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRules.txt @@ -0,0 +1,4 @@ +rules +ifUnfold +ifSplit +ifElseSplit diff --git a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/firstOrderRules.key b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/firstOrderRules.key index ef14af1896f..599a957014f 100644 --- a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/firstOrderRules.key +++ b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/firstOrderRules.key @@ -215,6 +215,11 @@ \heuristics(semantics_blasting) }; + intro { + \varcond(\newDependingOn(sk, t)) + \add(t = sk ==>) + }; + \lemma eqTermCut { \find(t) diff --git a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaHeader.key b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaHeader.key index 810c4692342..5b59a4205f2 100644 --- a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaHeader.key +++ b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaHeader.key @@ -28,4 +28,9 @@ alpha alpha::cast(any); boolean alpha::exactInstance(any); boolean alpha::instance(any); + // alpha alpha::_; // for matching in scripts +} + +\predicates { + // __; // for matching scripts } diff --git a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaRules.key b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaRules.key index d9633b05f09..a05891a7463 100644 --- a/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaRules.key +++ b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaRules.key @@ -500,16 +500,27 @@ tryCatchThrow { \find(\modality{#allmodal}{.. try { throw #se; #slist } catch ( #t #v0 ) { #slist1 } ...}\endmodality (post)) - \replacewith(\modality{#allmodal}{.. if ( #se == null ) { - try { throw new java.lang.NullPointerException (); } - catch ( #t #v0 ) { #slist1 } - } else if ( #se instanceof #t ) { - #t #v0; - #v0 = (#t) #se; - #slist1 - } else { - throw #se; - } ...}\endmodality (post)) + \sameUpdateLevel + + // Case NPE: throwing null fails: + "Null reference in throw": + \replacewith(\modality{#allmodal}{.. + try { throw new java.lang.NullPointerException (); } + catch ( #t #v0 ) { #slist1 } ... }\endmodality (post)) + \add( #se = null ==> ) ; + + // Case catch or throw: + "Normal execution" [main]: + \replacewith(\modality{#allmodal}{.. + if ( #se instanceof #t ) { + #t #v0; + #v0 = (#t) #se; + #slist1 + } else { + throw #se; + } ...}\endmodality (post)) + \add( ==> #se = null ) + \heuristics(simplify_prog) \displayname "tryCatchThrow" }; diff --git a/key.core/src/test/java/de/uka/ilkd/key/logic/TestLocalSymbols.java b/key.core/src/test/java/de/uka/ilkd/key/logic/TestLocalSymbols.java index 68e6f162503..31e2bfd7a85 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/logic/TestLocalSymbols.java +++ b/key.core/src/test/java/de/uka/ilkd/key/logic/TestLocalSymbols.java @@ -135,8 +135,8 @@ public void testDoubleInstantiation() throws Exception { Proof proof = env.getLoadedProof(); var script = env.getProofScript(); - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(null, proof); + ProofScriptEngine pse = new ProofScriptEngine(proof); + pse.execute(null, script); ImmutableList openGoals = proof.openGoals(); assert openGoals.size() == 1; diff --git a/key.core/src/test/java/de/uka/ilkd/key/proof/proverules/ProveRulesTest.java b/key.core/src/test/java/de/uka/ilkd/key/proof/proverules/ProveRulesTest.java index 88e87ee5f9e..71cbe36a283 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/proof/proverules/ProveRulesTest.java +++ b/key.core/src/test/java/de/uka/ilkd/key/proof/proverules/ProveRulesTest.java @@ -69,8 +69,8 @@ public void loadTacletProof(String tacletName, Taclet taclet, @Nullable Path pro KeyAst.ProofScript script = env.getProofScript(); if (script != null) { - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), proof); + ProofScriptEngine pse = new ProofScriptEngine(proof); + pse.execute(env.getUi(), script); } assertTrue(proof.closed(), "Taclet proof of taclet " + tacletName + " did not close."); diff --git a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProofCollections.java b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProofCollections.java index c255c3a1308..02a252f87b7 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProofCollections.java +++ b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProofCollections.java @@ -355,15 +355,12 @@ public static ProofCollection automaticJavaDL() throws IOException { g.provable("heap/removeDups/contains.key"); g.provable("heap/removeDups/removeDup.key"); g.provable("heap/saddleback_search/Saddleback_search.key"); - // TODO: Make BoyerMoore run automatically, not only loading proofs. Need proofs scripts for - // that. - g.loadable("heap/BoyerMoore/BM(BM__bm((I)).JML normal_behavior operation contract.0.proof"); - g.loadable( - "heap/BoyerMoore/BM(BM__count((I,_bigint,_bigint)).JML accessible clause.0.proof"); - g.loadable( - "heap/BoyerMoore/BM(BM__count((I,_bigint,_bigint)).JML model_behavior operation contract.0.proof"); - g.loadable( - "heap/BoyerMoore/BM(BM__monoLemma((I,int,int)).JML normal_behavior operation contract.0.proof"); + // DONE: Make BoyerMoore run automatically, not only loading proofs. Need proofs scripts for + // that. YESSS, it runs with scripts now ... + g.provable("heap/BoyerMoore/BM.bm.key"); + g.provable("heap/BoyerMoore/BM.count.accessible.key"); + g.provable("heap/BoyerMoore/BM.count.key"); + g.provable("heap/BoyerMoore/BM.monoLemma.key"); g = c.group("quicksort"); g.setLocalSettings("[Choice]DefaultChoices=moreSeqRules-moreSeqRules:on"); diff --git a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProveTest.java b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProveTest.java index 7ccecc800fb..712e3ea75cb 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProveTest.java +++ b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/ProveTest.java @@ -190,8 +190,8 @@ private void autoMode(KeYEnvironment env, Proof loa env.getProofControl().startAndWaitForAutoMode(loadedProof); } else { // ... script - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), env.getLoadedProof()); + ProofScriptEngine pse = new ProofScriptEngine(env.getLoadedProof()); + pse.execute(env.getUi(), script); } } diff --git a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/proofcollection/TestFile.java b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/proofcollection/TestFile.java index 79df3f4b495..1cf3e073883 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/proofcollection/TestFile.java +++ b/key.core/src/test/java/de/uka/ilkd/key/proof/runallproofs/proofcollection/TestFile.java @@ -268,8 +268,8 @@ protected void autoMode(KeYEnvironment env, Proof l env.getProofControl().startAndWaitForAutoMode(loadedProof); } else { // ... script - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), env.getLoadedProof()); + ProofScriptEngine pse = new ProofScriptEngine(env.getLoadedProof()); + pse.execute(env.getUi(), script); } } diff --git a/key.core/src/test/java/de/uka/ilkd/key/rule/merge/MergeRuleTests.java b/key.core/src/test/java/de/uka/ilkd/key/rule/merge/MergeRuleTests.java index 773aec80e7f..da839d8719b 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/rule/merge/MergeRuleTests.java +++ b/key.core/src/test/java/de/uka/ilkd/key/rule/merge/MergeRuleTests.java @@ -144,6 +144,10 @@ public void testDoAutomaticGcdProofWithMergePointStatementAndBlockContract() { * has to result in a renaming. An interactive cut in the proof should make sure that the * renaming works and resolves the clashes. The test case includes a "is weakening" goal. * Underlying Java file: "A.java". + * + * Unfortunately, this test case was broken by a change in the taclet database. I replaced the + * proof file by the automatic proof for the same proof obligation, hoping that the same issue + * is still covered. M.U. 2/2026 */ @Test public void testLoadProofWithDiffVarsWithSameNameAndMPS() { diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/DocumentationGenerator.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/DocumentationGenerator.java new file mode 100644 index 00000000000..ff60686b5f8 --- /dev/null +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/DocumentationGenerator.java @@ -0,0 +1,113 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.io.FileNotFoundException; +import java.util.*; + +import de.uka.ilkd.key.util.KeYResourceManager; + +public class DocumentationGenerator { + + private static String branch; + private static String sha1; + + public static void main(String[] args) throws FileNotFoundException { + + if (args.length > 0) { + System.err.println("Redirecting output to " + args[0]); + System.setOut(new java.io.PrintStream(args[0])); + } + + printHeader(); + + Set> commands = + ProofScriptEngine.loadCommands().entrySet(); + Map>> commandsByCategory = + new TreeMap<>(); + + for (Map.Entry entry : commands) { + String category = entry.getValue().getCategory(); + if (category == null) { + category = "Uncategorized"; + } + commandsByCategory.computeIfAbsent(category, k -> new ArrayList<>()).add(entry); + } + + List categories = new ArrayList<>(commandsByCategory.keySet()); + categories.remove("Uncategorized"); + Collections.sort(categories); + + for (String category : categories) { + listCategory(category, commandsByCategory.get(category)); + } + + listCategory("Uncategorized", commandsByCategory.get("Uncategorized")); + + } + + private static void printHeader() { + branch = KeYResourceManager.getManager().getBranch(); + String version = KeYResourceManager.getManager().getVersion(); + sha1 = KeYResourceManager.getManager().getSHA1(); + + // This gets too technical. But this is for the key-docs repository. ... + System.out.printf( + """ + + # Proof Script Commands + + This document lists all proof script commands available in the KeY system. + The general ideas of scripts, their syntax, and control flow are described + in the general documentation files on proof scripts. + + Field | Value + ----- | ----- + Generated on: | %s + Branch: | %s + Version: | %s + Commit: | %s + + The commands are organised into categories. Each command may have multiple aliases + under which it can be invoked. The first alias listed is the primary name of the command. + There *named* and *positional* arguments. Named arguments need to be prefixed by their name + and a colon. Positional arguments are given in the order defined by the command. + Optional arguments are enclosed in square brackets. + """, + new Date(), branch, version, sha1); + } + + private static void listCategory(String category, + List> proofScriptCommands) { + proofScriptCommands.sort(Map.Entry.comparingByKey()); + System.out.println("\n## Category *" + category + "*\n"); + for (Map.Entry entry : proofScriptCommands) { + System.out.println("
\n"); + if (entry.getKey().equals(entry.getValue().getName())) { + ProofScriptCommand command = entry.getValue(); + String link = "main".equals(branch) ? "main" : sha1; + System.out + .println("### Command `" + + command.getName() + "`\n\n"); + System.out.printf( + "[Source](https://github.com/KeYProject/key/blob/%s/key.core/src/main/java/%s.java)\n\n", + link, + command.getClass().getName().replace('.', '/')); + System.out.println(command.getDocumentation() + "\n"); + if (command.getAliases().size() > 1) { + System.out.println( + "#### Aliases:\n" + String.join(", ", command.getAliases()) + "\n"); + } + } else { + System.out + .println("### Command `" + + entry.getKey() + "`\n"); + System.out.println("Alias for command [\u2192 `" + entry.getValue().getName() + + "`](#command-" + entry.getValue().getName() + ")\n"); + } + } + } +} diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/FocusCommandTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/FocusCommandTest.java index 612d69ac541..8d2469c4efb 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/scripts/FocusCommandTest.java +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/FocusCommandTest.java @@ -32,8 +32,8 @@ public void testSimpleSelection() throws Exception { KeYEnvironment env = KeYEnvironment.load(temp); Proof p = env.getLoadedProof(); var script = ParsingFacade.parseScript("macro \"nosplit-prop\"; focus (i=1 ==> i = 4);"); - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), p); + ProofScriptEngine pse = new ProofScriptEngine(p); + pse.execute(env.getUi(), script); assertEquals(1, p.openGoals().size()); Goal g = p.openGoals().head(); @@ -53,8 +53,8 @@ public void testSelectionWithLabels() throws Exception { KeYEnvironment env = KeYEnvironment.load(temp); Proof p = env.getLoadedProof(); var script = ParsingFacade.parseScript("macro \"nosplit-prop\"; focus (i=1 ==> i = 3);"); - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), p); + ProofScriptEngine pse = new ProofScriptEngine(p); + pse.execute(env.getUi(), script); assertEquals(1, p.openGoals().size()); Goal g = p.openGoals().head(); diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java new file mode 100644 index 00000000000..503ff1cd2eb --- /dev/null +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java @@ -0,0 +1,135 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.scripts; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.uka.ilkd.key.control.DefaultUserInterfaceControl; +import de.uka.ilkd.key.control.KeYEnvironment; +import de.uka.ilkd.key.nparser.KeyAst; +import de.uka.ilkd.key.proof.io.ProofSaver; +import de.uka.ilkd.key.util.KeYConstants; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JmlScriptTest { + + private static final Path KEY_FILE; + private static final Logger LOGGER = LoggerFactory.getLogger(JmlScriptTest.class); + + // Set this to a specific case to only run that case for debugging + private static final String ONLY_CASE = System.getProperty("key.testJmlScript.only"); + // Set this to true to save the proof after running the script + private static final boolean SAVE_PROOF = false; + + static { + URL url = JmlScriptTest.class.getResource("jml/project.key"); + try { + KEY_FILE = Paths.get(url.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @ParameterizedTest(name = "{1}") + @MethodSource("filesProvider") + public void testJmlScript(Path path, String identifier) throws Exception { + + Parameters params = readParams(path); + + Path tmpDir = Files.createTempDirectory("key.jmltest."); + try { + Files.copy(path, tmpDir.resolve("Test.java")); + Path projectFile = tmpDir.resolve("project.key"); + Files.copy(KEY_FILE, projectFile); + KeYEnvironment env = KeYEnvironment.load(projectFile); + if (params.settings != null && !params.settings.isEmpty()) { + for (Map.Entry entry : params.settings.entrySet()) { + env.getLoadedProof().getSettings().getStrategySettings() + .getActiveStrategyProperties() + .setProperty(entry.getKey(), entry.getValue()); + } + } + KeyAst.ProofScript script = env.getProofScript(); + if (script != null) { + ProofScriptEngine pse = new ProofScriptEngine(env.getLoadedProof()); + pse.execute(env.getUi(), script); + } + + if (SAVE_PROOF) { + String filename = tmpDir.resolve("saved.proof").toString(); + ProofSaver saver = + new ProofSaver(env.getLoadedProof(), filename, KeYConstants.INTERNAL_VERSION); + saver.save(); + LOGGER.info("Saved proof to {}", filename); + } + + if (params.shouldClose) { + Assertions.assertTrue(env.getLoadedProof().closed(), "Proof did not close."); + } else { + Assertions.assertFalse(env.getLoadedProof().closed(), "Proof closes unexpectedly."); + } + } finally { + // Uncomment the following line to delete the temporary directory after the test + if (params.deleteTmpDir && !SAVE_PROOF) { + LOGGER.info("Deleting temporary directory: {}", tmpDir); + Files.walk(tmpDir).sorted(Comparator.reverseOrder()).map(Path::toFile) + .forEach(File::delete); + } else { + LOGGER.info("Temporary directory retained for inspection: {}", tmpDir); + } + } + + } + + private static Parameters readParams(Path path) throws IOException { + String input = Files.lines(path).filter(l -> l.startsWith("//!")).map(l -> l.substring(3)) + .collect(Collectors.joining("\n")).trim(); + if (input.isEmpty()) { + return new Parameters(); + } + var objectMapper = new ObjectMapper(new YAMLFactory()); + objectMapper.findAndRegisterModules(); + return objectMapper.readValue(input, Parameters.class); + } + + public static Stream filesProvider() throws URISyntaxException, IOException { + URL jmlUrl = JmlScriptTest.class.getResource("jml"); + if (ONLY_CASE != null) { + return Stream.of(Arguments.of(Paths.get(jmlUrl.toURI()).resolve(ONLY_CASE), + "single specified case: " + ONLY_CASE)); + } else { + return Files.list(Paths.get(jmlUrl.toURI())) + .filter(p -> p.toString().endsWith(".java")) + .map(p -> Arguments.of(p, p.getFileName().toString())); + } + } + + static class Parameters { + public boolean shouldClose = true; + public String method; + public String exception; + public boolean deleteTmpDir = true; + public Map settings; + } + + +} diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/TestProofScriptCommand.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/TestProofScriptCommand.java index 34ae12c0c6a..4bf11721adc 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/scripts/TestProofScriptCommand.java +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/TestProofScriptCommand.java @@ -10,9 +10,12 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; import de.uka.ilkd.key.control.DefaultUserInterfaceControl; import de.uka.ilkd.key.control.KeYEnvironment; +import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.nparser.ParsingFacade; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; @@ -27,6 +30,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -35,15 +40,33 @@ * see {@link MasterHandlerTest} from where I copied quite a bit. */ public class TestProofScriptCommand { + + private static final String ONLY_CASES = System.getProperty("key.testProofScript.only"); + private static final Logger LOGGER = LoggerFactory.getLogger(TestProofScriptCommand.class); + public record TestInstance( String name, - String key, String script, @Nullable String exception, - String[] goals, Integer selectedGoal) { + String key, + String script, + @Nullable String exception, + String[] goals, + Integer selectedGoal) { } public static List data() throws IOException, URISyntaxException { var folder = Paths.get("src/test/resources/de/uka/ilkd/key/scripts/cases") .toAbsolutePath(); + + Predicate filter; + if (ONLY_CASES != null && !ONLY_CASES.isEmpty()) { + // if ONLY_CASES is set, only run those cases (comma separated) + Set only = Set.of(ONLY_CASES.split(" *, *")); + filter = p -> only.contains( + p.getFileName().toString().substring(0, p.getFileName().toString().length() - 4)); + } else { + filter = p -> true; + } + try (var walker = Files.walk(folder)) { List files = walker.filter(it -> it.getFileName().toString().endsWith(".yml")).toList(); @@ -52,10 +75,15 @@ public static List data() throws IOException, URISyntaxException { List args = new ArrayList<>(files.size()); for (Path path : files) { + if (!filter.test(path)) { + continue; + } try { TestInstance instance = objectMapper.readValue(path.toFile(), TestInstance.class); - args.add(Arguments.of(instance)); + var name = instance.name == null ? path.getFileName().toString().substring(0, + path.getFileName().toString().length() - 4) : instance.name; + args.add(Arguments.of(instance, name)); } catch (Exception e) { System.out.println(path); e.printStackTrace(); @@ -66,24 +94,25 @@ public static List data() throws IOException, URISyntaxException { } } - @ParameterizedTest + @ParameterizedTest(name = "{1}") @MethodSource("data") - void testProofScript(TestInstance data) throws Exception { - var name = data.name(); + void testProofScript(TestInstance data, String name) throws Exception { Path tmpKey = Files.createTempFile("proofscript_key_" + name, ".key"); + LOGGER.info("Testing {} using file", name, tmpKey); Files.writeString(tmpKey, data.key()); KeYEnvironment env = KeYEnvironment.load(tmpKey); Proof proof = env.getLoadedProof(); - var script = ParsingFacade.parseScript(data.script()); - ProofScriptEngine pse = new ProofScriptEngine(script); + KeyAst.ProofScript script = ParsingFacade.parseScript(data.script()); + ProofScriptEngine pse = new ProofScriptEngine(proof); boolean hasException = data.exception() != null; try { - pse.execute(env.getUi(), proof); + pse.execute(env.getUi(), script); } catch (ScriptException ex) { + ex.printStackTrace(); assertTrue(data.exception != null && !data.exception.isEmpty(), "An exception was not expected, but got " + ex.getMessage()); // weigl: fix spurious error on Windows machine due to different file endings. @@ -104,15 +133,21 @@ void testProofScript(TestInstance data) throws Exception { Assertions.assertEquals(expected, goals.size()); for (String expectedGoal : data.goals()) { - assertThat(goals.head().toString().trim()).isEqualTo(expectedGoal); + assertThat(normaliseSpace(goals.head().toString())).isEqualTo(expectedGoal); goals = goals.tail(); } if (data.selectedGoal() != null) { Goal goal = pse.getStateMap().getFirstOpenAutomaticGoal(); - assertThat(goal.toString().trim()).isEqualTo(data.goals()[data.selectedGoal()]); + assertThat(normaliseSpace(goal.toString())) + .isEqualTo(data.goals()[data.selectedGoal()]); } } } + // For some layout reasons the toString may add linebreaks and spaces + private static String normaliseSpace(String str) { + return str.replaceAll("\\s+", " ").trim(); + } + } diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/RewriteTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/RewriteTest.java index 19f094bfbc4..4c2e621c087 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/RewriteTest.java +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/RewriteTest.java @@ -42,8 +42,8 @@ public void testTransitive() assertNotNull(env); Proof p = env.getLoadedProof(); - ProofScriptEngine engine = new ProofScriptEngine(script); - engine.execute(env.getUi(), p); + ProofScriptEngine engine = new ProofScriptEngine(p); + engine.execute(env.getUi(), script); String firstOpenGoal = p.openGoals().head().sequent().toString(); String expectedSequent = "[equals(x,f),equals(x,z)]==>[equals(z,f)]"; @@ -69,8 +69,8 @@ public void testLessTransitive() KeYEnvironment env = KeYEnvironment.load(keyFile); Proof proof = env.getLoadedProof(); - ProofScriptEngine engine = new ProofScriptEngine(script); - engine.execute(env.getUi(), proof); + ProofScriptEngine engine = new ProofScriptEngine(proof); + engine.execute(env.getUi(), script); String firstOpenGoal = proof.openGoals().head().sequent().toString(); String expectedSequent = "[]==>[imp(and(gt(x,f),lt(x,z)),lt(f,z))]"; diff --git a/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/ValueInjectorTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/ValueInjectorTest.java index cbb384d3b48..c2949ca1b59 100644 --- a/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/ValueInjectorTest.java +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/meta/ValueInjectorTest.java @@ -58,10 +58,43 @@ public void testRequired() { () -> ValueInjector.injection(new PPCommand(), pp, ast)); } + // copied from old jmlScript branch ... possibly needs adaptation + @Test + public void testUnknownArguments() { + PP pp = new PP(); + Map args = new HashMap<>(); + ScriptCommandAst ast = new ScriptCommandAst("pp", args, new LinkedList<>(), + null); + args.put("i", "42"); + args.put("b", "true"); + args.put("q", "requiredValue"); + args.put("unknownParameter", "unknownValue"); + assertThrows(UnknownArgumentException.class, + () -> ValueInjector.injection(new PPCommand(), pp, ast)); + } + + // copied from old jmlScript branch ... possibly needs adaptation + @Test + public void testVarargsOld() throws Exception { + PP pp = new PP(); + Map args = new HashMap<>(); + ScriptCommandAst ast = new ScriptCommandAst("pp", args, new LinkedList<>(), + null); + args.put("i", "42"); + args.put("b", "true"); + args.put("var_21", "21"); + args.put("q", "requiredValue"); + args.put("var_other", "otherString"); + ValueInjector.injection(new PPCommand(), pp, ast); + assertEquals("21", pp.varargs.get("var_21")); + assertEquals("otherString", pp.varargs.get("var_other")); + assertEquals(2, pp.varargs.size()); + } + @Test public void testInferScriptArguments() throws NoSuchFieldException { List meta = ArgumentsLifter.inferScriptArguments(PP.class); - assertEquals(4, meta.size()); + assertEquals(5, meta.size()); { ProofScriptArgument b = meta.getFirst(); @@ -80,18 +113,35 @@ public void testInferScriptArguments() throws NoSuchFieldException { } { - ProofScriptArgument i = meta.get(2); - assertEquals("s", i.getName()); - assertEquals(PP.class.getDeclaredField("s"), i.getField()); - assertEquals(String.class, i.getType()); - assertFalse(i.isRequired()); + ProofScriptArgument s = meta.get(2); + assertEquals("s", s.getName()); + assertEquals(PP.class.getDeclaredField("s"), s.getField()); + assertEquals(String.class, s.getType()); + assertFalse(s.isRequired()); + } + + { + ProofScriptArgument q = meta.get(3); + assertEquals("q", q.getName()); + assertEquals(PP.class.getDeclaredField("required"), q.getField()); + assertEquals(String.class, q.getType()); + assertTrue(q.isRequired()); + } + + { + ProofScriptArgument vars = meta.get(4); + assertEquals("varargs", vars.getName()); + assertEquals(PP.class.getDeclaredField("varargs"), vars.getField()); + assertEquals(Map.class, vars.getType()); + assertEquals("var_", vars.getOptionalVarArgs().prefix()); + assertSame(String.class, vars.getOptionalVarArgs().as()); + assertTrue(vars.isOptionalVarArgs()); } } @Test - public void testFlag() throws ConversionException, ArgumentRequiredException, - InjectionReflectionException, NoSpecifiedConverterException { + public void testFlag() throws Exception { class Options { @Flag boolean a; @@ -110,8 +160,7 @@ class Options { @Test - public void testVarargs() throws ConversionException, ArgumentRequiredException, - InjectionReflectionException, NoSpecifiedConverterException { + public void testVarargs() throws InjectionException { class Varargs { @OptionalVarargs(prefix = "a", as = Boolean.class) Map a; @@ -149,6 +198,9 @@ public static class PP { @Option("q") @MonotonicNonNull String required; + + @OptionalVarargs(prefix = "var_") + Map varargs; } @NullMarked diff --git a/key.core/src/test/resources/de/uka/ilkd/key/nparser/taclets.old.txt b/key.core/src/test/resources/de/uka/ilkd/key/nparser/taclets.old.txt index a95305f3dbf..3945f106190 100644 --- a/key.core/src/test/resources/de/uka/ilkd/key/nparser/taclets.old.txt +++ b/key.core/src/test/resources/de/uka/ilkd/key/nparser/taclets.old.txt @@ -1,5 +1,5 @@ # This files contains representation of taclets, which are accepted and revised. -# Date: Tue Jul 29 22:37:48 CEST 2025 +# Date: Tue Feb 17 23:36:47 CET 2026 == abortJavaCardTransactionAPI (abortJavaCardTransactionAPI) ========================================= abortJavaCardTransactionAPI { @@ -17989,20 +17989,22 @@ tryCatchThrow { #slist1 } ... }}| (post)) -\replacewith(#allmodal ((modal operator))|{{ .. - if (#se == null) { - try { - throw new java.lang.NullPointerException(); - } catch (#t #v0) { - #slist1 - } - } else if (#se instanceof #t) { +\sameUpdateLevel +\add []==>[equals(#se,null)] \replacewith(#allmodal ((modal operator))|{{ .. + if (#se instanceof #t) { #t #v0; #v0 = (#t) #se; #slist1 } else { throw #se; } +... }}| (post)) ; +\add [equals(#se,null)]==>[] \replacewith(#allmodal ((modal operator))|{{ .. + try { + throw new java.lang.NullPointerException(); + } catch (#t #v0) { + #slist1 + } ... }}| (post)) \heuristics(simplify_prog) Choices: programRules:Java} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/assumes.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/assumes.yml new file mode 100644 index 00000000000..6374ed61d06 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/assumes.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a,b,c,d,e,f; } + \problem { a=b, a=d ==> a=c } +script: | + rule "applyEq" on:(?focus(a) = c) assumes:( ? = b ==> ) ; +goals: + - a = b, a = d ==> b = c diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/autoOnly.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/autoOnly.yml new file mode 100644 index 00000000000..24eef5e7d10 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/autoOnly.yml @@ -0,0 +1,7 @@ +key: | + \predicates { a; b; c;} + \problem { a & b & 1=0 -> a | c } +script: | + auto only:alpha; +goals: + - a, b, 1 = 0 ==> a, c \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/bugWithFunctions.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/bugWithFunctions.yml new file mode 100644 index 00000000000..5f4b6929552 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/bugWithFunctions.yml @@ -0,0 +1,12 @@ +# Was a bug: b+0 would also match here ... +key: | + \programVariables { int a; int b; } + + \problem { a + 0 = a & b + 0 = b } + +script: | + rule add_zero_right on:(a+0); + +goals: + - "==> a = a & b + 0 = b" + diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find1.yml new file mode 100644 index 00000000000..26ea900fee9 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find1.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a; } + \problem { ==> ((1 + 1) + 5) + 0 = 7 & ((0 + 2) + 5) + 0 = 7 } +script: | + rule add_zero_right on:(?find(1+1)); +goals: + - ==> 1 + 1 + 5 = 7 & 0 + 2 + 5 + 0 = 7 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find2.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find2.yml new file mode 100644 index 00000000000..498842ca2e8 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find2.yml @@ -0,0 +1,6 @@ +key: | + \problem { ==> 1 + 0 = 1 & 2 + 0 = 2 & 3 + 0 = 3 } +script: | + rule add_zero_right on:(?find(2)); +goals: + - ==> 1 + 0 = 1 & 2 = 2 & 3 + 0 = 3 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/findOfFocus.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/findOfFocus.yml new file mode 100644 index 00000000000..3c9e122033b --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/findOfFocus.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a; } + \problem { ==> 3 = (3+0)+0 & 3+0 = (3+0)+0 } +script: | + rule add_zero_right on:(? & ?find(?focus(3+0)+0)); +goals: + - ==> 3 = 3 + 0 + 0 & 3 + 0 = 3 + 0 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus1.yml new file mode 100644 index 00000000000..3387b6cd31e --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus1.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a; } + \problem { ==> a + 0 = a + 0 & 1 = 1 } +script: | + rule add_zero_right on:(a+0 = ?focus(a+0)); +goals: + - ==> a + 0 = a & 1 = 1 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus2.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus2.yml new file mode 100644 index 00000000000..f776bc20567 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus2.yml @@ -0,0 +1,8 @@ +# Used to identify and hunt a bug. +# Raised a -1 illegal index exception +key: | + \problem { ==> 1 + 0 + 0 + 0 + 0 >= 0 } +script: | + rule add_zero_right on:(?focus(?) + ? + ? + ?); +goals: + - ==> 1 + 0 + 0 + 0 >= 0 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focusOfFind.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focusOfFind.yml new file mode 100644 index 00000000000..267325d1c12 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focusOfFind.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a; } + \problem { ==> (0+3)+0 = (0+2+1)+0 & (0+1+1+1)+0 = (0+3)+0 } +script: | + rule add_zero_right on:(? = ?focus(?find(3))); +goals: + - ==> 0 + 3 + 0 = 0 + 2 + 1 + 0 & 0 + 1 + 1 + 1 + 0 = 0 + 3 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus_rule.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus_rule.yml new file mode 100644 index 00000000000..f7ad12aab7d --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus_rule.yml @@ -0,0 +1,6 @@ +key: | + \problem { 1 > 0, 2 > 1 ==> 3 > 2, 4 > 2, 5 > 1 } +script: | + focus (? > 1 ==> ? > 2); +goals: + - 2 > 1 ==> 3 > 2, 4 > 2 diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml new file mode 100644 index 00000000000..c73fcf31c8d --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml @@ -0,0 +1,6 @@ +key: | + \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } +script: | + witness (\forall int x; (? | x < ?)) as="y"; +goals: + - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes2.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes2.yml new file mode 100644 index 00000000000..00bbff5a86b --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes2.yml @@ -0,0 +1,7 @@ +# Holes in strings ... requires a bit of care +key: | + \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } +script: | + witness "(\forall int x; (? | x < ?))" as="y"; +goals: + - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/instantiate1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/instantiate1.yml new file mode 100644 index 00000000000..66bebb3403e --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/instantiate1.yml @@ -0,0 +1,6 @@ +key: | + \problem { \exists int x; x>0 } +script: | + instantiate var="x" with=3 hide:true; +goals: + - ==> 3 > 0 diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/let.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/let.yml new file mode 100644 index 00000000000..990640ac6a1 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/let.yml @@ -0,0 +1,7 @@ +key: | + \problem { 1+0 = 2+0 } +script: | + let @p1: "1+0"; + rule add_zero_right on: @p1; +goals: + - ==> 1 = 2 + 0 diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/termsAsStrings.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/termsAsStrings.yml new file mode 100644 index 00000000000..5d36b39c5db --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/termsAsStrings.yml @@ -0,0 +1,8 @@ +# Give arguments as strings ... requires a bit of care +key: | + \problem { ==> true } +script: | + cut "1 > 0"; +goals: + - ==> 1 > 0, true + - 1 > 0 ==> true \ No newline at end of file diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness1.yml new file mode 100644 index 00000000000..da74c45bd51 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness1.yml @@ -0,0 +1,6 @@ +key: | + \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } +script: | + witness (\forall int x; (x>0 | x<2)) as="y"; +goals: + - ==> \forall int x; x = x, y > 0 | y < 2 diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness2.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness2.yml new file mode 100644 index 00000000000..69e2d245925 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness2.yml @@ -0,0 +1,8 @@ +key: | + \functions { int x; int x1; } + \predicates { x2; } + \programVariables { int x3; } + \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < x1) } +script: | + witness (\forall int x; (x > 0 | x < x1)) as="x1"; +exception: "Name already used as function or predicate: x1" diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AssertedModelMethod.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AssertedModelMethod.java new file mode 100644 index 00000000000..f10551b4dc7 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AssertedModelMethod.java @@ -0,0 +1,23 @@ +//! settings: +//! CLASS_AXIOM_OPTIONS_KEY: CLASS_AXIOM_OFF + +// Was a bug: +// Instantiation Test::pred(heap,self,int::select(heap,self,Test::$f)) of cutFormula (formula) does not satisfy the variable conditions + +class Test { + + //@ model boolean pred(int x) { return x > 20; } + + int f; + + //@ requires pred(f); + //@ ensures f > 2; + void test() { + int x; + /*@ assert f > 2 \by { + assert pred(f) \by { auto classAxioms:true; } + auto; // should not be necessary ... eventually removed + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly.java new file mode 100644 index 00000000000..445fb66b9a5 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly.java @@ -0,0 +1,19 @@ +//! settings: +//! CLASS_AXIOM_OPTIONS_KEY: CLASS_AXIOM_OFF + +class Test { + + //@ model int f(int arg) { return arg + arg; } + + boolean b,c,d; + + //@ ensures true; + void test() { + + /*@ assert b && c ==> c || d \by { + auto only:"alpha"; + rule "close" occ:"1"; + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java new file mode 100644 index 00000000000..8be9e20db47 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java @@ -0,0 +1,17 @@ +//! shouldClose: false + +class Test { + + //@ model int f(int arg) { return arg + arg; } + + boolean b,c,d; + + //@ ensures true; + void test() { + + /*@ assert b && c ==> c || d \by { + auto only:"beta"; + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/NestedAssert.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/NestedAssert.java new file mode 100644 index 00000000000..76a6686eddd --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/NestedAssert.java @@ -0,0 +1,11 @@ +class Test { + //@ ensures true; + void test() { + int x; + /*@ assert x > 2 \by { + assert x == 7 \by { cheat; } + auto; // should not be necessary ... eventually removed + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/Obtain1.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/Obtain1.java new file mode 100644 index 00000000000..92bd352ddf2 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/Obtain1.java @@ -0,0 +1,15 @@ +//! deleteTmpDir : false + +class Test { + //@ ensures true; + void test() { + int x = 42; + /*@ assert x == 42 \by { + obtain int y = 41; + assert y+1 == 42 \by auto; + auto; + // Still too verbose on auto + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainFromGoal.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainFromGoal.java new file mode 100644 index 00000000000..4f7350b56f1 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainFromGoal.java @@ -0,0 +1,18 @@ +//! deleteTmpDir : false + +class Test { + + //@ model int f(int arg); + + //@ ensures true; + void test() { + int x = 42; + /*@ assert (\forall int x; f(x) > 40) \by { + obtain int y \from_goal; + assert f(y) == 42 \by cheat; + auto; + // Still too verbose on auto + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainWithUpdates.java b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainWithUpdates.java new file mode 100644 index 00000000000..c85eebaee8b --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainWithUpdates.java @@ -0,0 +1,23 @@ +//! settings: +//! CLASS_AXIOM_OPTIONS_KEY: CLASS_AXIOM_OFF + +class Test { + + //@ model int f(int arg) { return arg + arg; } + + int field; + + //@ ensures true; + void test() { + + field = 21; + int local = 42; + + /*@ assert f(field) == local \by { + expand on: f(field); + auto; + // Still too verbose on auto + } */ + } + +} diff --git a/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/project.key b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/project.key new file mode 100644 index 00000000000..dad8dd3de51 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/project.key @@ -0,0 +1,14 @@ +\profile "Java Profile"; + + +\javaSource "."; + +\proofObligation { + "class" : "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", + "contract" : "Test[Test::test()].JML operation contract.0", + "name" : "Test[Test::test()].JML operation contract.0" + } + +\proofScript { + macro "script-auto"; +} diff --git a/key.core/src/test/resources/testcase/merge/A.differentVarsWithSameName.MPS.cut.closed.proof b/key.core/src/test/resources/testcase/merge/A.differentVarsWithSameName.MPS.cut.closed.proof index 1e848f03b62..38635c8aada 100644 --- a/key.core/src/test/resources/testcase/merge/A.differentVarsWithSameName.MPS.cut.closed.proof +++ b/key.core/src/test/resources/testcase/merge/A.differentVarsWithSameName.MPS.cut.closed.proof @@ -1,60 +1,97 @@ \profile "Java Profile"; -\settings { -"#Proof-Settings-Config-File -#Tue May 02 14:36:15 CEST 2017 -[StrategyProperty]VBT_PHASE=VBT_SYM_EX -[SMTSettings]useUninterpretedMultiplication=true -[SMTSettings]SelectedTaclets=\\#begboolean_equal_2\\#end,\\#begboolean_not_equal_1\\#end,\\#begboolean_not_equal_2\\#end,\\#begtrue_to_not_false\\#end,\\#begfalse_to_not_true\\#end,\\#begboolean_true_commute\\#end,\\#begboolean_false_commute\\#end,\\#begapply_eq_boolean\\#end,\\#begapply_eq_boolean_2\\#end,\\#begapply_eq_boolean_rigid\\#end,\\#begapply_eq_boolean_rigid_2\\#end,\\#begexpandInByte\\#end,\\#begexpandInChar\\#end,\\#begexpandInShort\\#end,\\#begexpandInInt\\#end,\\#begexpandInLong\\#end,\\#begreplace_byte_MAX\\#end,\\#begreplace_byte_MIN\\#end,\\#begreplace_char_MAX\\#end,\\#begreplace_char_MIN\\#end,\\#begreplace_short_MAX\\#end,\\#begreplace_short_MIN\\#end,\\#begreplace_int_MAX\\#end,\\#begreplace_int_MIN\\#end,\\#begreplace_long_MAX\\#end,\\#begreplace_long_MIN\\#end,\\#begreplace_byte_RANGE\\#end,\\#begreplace_byte_HALFRANGE\\#end,\\#begreplace_short_RANGE\\#end,\\#begreplace_short_HALFRANGE\\#end,\\#begreplace_char_RANGE\\#end,\\#begreplace_int_RANGE\\#end,\\#begreplace_int_HALFRANGE\\#end,\\#begreplace_long_RANGE\\#end,\\#begreplace_long_HALFRANGE\\#end,\\#begtranslateJavaUnaryMinusInt\\#end,\\#begtranslateJavaUnaryMinusLong\\#end,\\#begtranslateJavaBitwiseNegation\\#end,\\#begtranslateJavaAddInt\\#end,\\#begtranslateJavaAddLong\\#end,\\#begtranslateJavaSubInt\\#end,\\#begtranslateJavaSubLong\\#end,\\#begtranslateJavaMulInt\\#end,\\#begtranslateJavaMulLong\\#end,\\#begtranslateJavaMod\\#end,\\#begtranslateJavaDivInt\\#end,\\#begtranslateJavaDivLong\\#end,\\#begtranslateJavaCastByte\\#end,\\#begtranslateJavaCastShort\\#end,\\#begtranslateJavaCastInt\\#end,\\#begtranslateJavaCastLong\\#end,\\#begtranslateJavaCastChar\\#end,\\#begtranslateJavaShiftRightInt\\#end,\\#begtranslateJavaShiftRightLong\\#end,\\#begtranslateJavaShiftLeftInt\\#end,\\#begtranslateJavaShiftLeftLong\\#end,\\#begtranslateJavaUnsignedShiftRightInt\\#end,\\#begtranslateJavaUnsignedShiftRightLong\\#end,\\#begtranslateJavaBitwiseOrInt\\#end,\\#begtranslateJavaBitwiseOrLong\\#end,\\#begtranslateJavaBitwiseAndInt\\#end,\\#begtranslateJavaBitwiseAndLong\\#end,\\#begtranslateJavaBitwiseXOrInt\\#end,\\#begtranslateJavaBitwiseXOrLong\\#end,\\#begcastDel\\#end,\\#begtypeEq\\#end,\\#begtypeEqDerived\\#end,\\#begtypeEqDerived2\\#end,\\#begtypeStatic\\#end,\\#begcloseType\\#end,\\#begcloseTypeSwitched\\#end,\\#begexact_instance_definition_int\\#end,\\#begexact_instance_definition_boolean\\#end,\\#begexact_instance_definition_null\\#end,\\#begexact_instance_for_interfaces_or_abstract_classes\\#end,\\#begclass_being_initialized_is_prepared\\#end,\\#beginitialized_class_is_prepared\\#end,\\#beginitialized_class_is_not_erroneous\\#end,\\#begclass_initialized_excludes_class_init_in_progress\\#end,\\#begclass_erroneous_excludes_class_in_init\\#end,\\#begerroneous_class_has_no_initialized_sub_class\\#end,\\#begsuperclasses_of_initialized_classes_are_prepared\\#end,\\#begelementOfEmpty\\#end,\\#begelementOfAllLocs\\#end,\\#begelementOfSingleton\\#end,\\#begelementOfUnion\\#end,\\#begelementOfIntersect\\#end,\\#begelementOfSetMinus\\#end,\\#begelementOfAllFields\\#end,\\#begelementOfAllObjects\\#end,\\#begelementOfArrayRange\\#end,\\#begelementOfFreshLocs\\#end,\\#begequalityToElementOf\\#end,\\#begsubsetToElementOf\\#end,\\#begdisjointToElementOf\\#end,\\#begcreatedInHeapToElementOf\\#end,\\#begelementOfEmptyEQ\\#end,\\#begelementOfAllLocsEQ\\#end,\\#begelementOfSingletonEQ\\#end,\\#begelementOfUnionEQ\\#end,\\#begelementOfIntersectEQ\\#end,\\#begelementOfSetMinusEQ\\#end,\\#begelementOfAllFieldsEQ\\#end,\\#begelementOfAllObjectsEQ\\#end,\\#begelementOfArrayRangeEQ\\#end,\\#begelementOfFreshLocsEQ\\#end,\\#begunionWithEmpty1\\#end,\\#begunionWithEmpty2\\#end,\\#begunionWithAllLocs1\\#end,\\#begunionWithAllLocs2\\#end,\\#begintersectWithEmpty1\\#end,\\#begintersectWithEmpty2\\#end,\\#begintersectWithAllLocs1\\#end,\\#begintersectWithAllLocs2\\#end,\\#begsetMinusWithEmpty1\\#end,\\#begsetMinusWithEmpty2\\#end,\\#begsetMinusWithAllLocs\\#end,\\#begsubsetWithEmpty\\#end,\\#begsubsetWithAllLocs\\#end,\\#begdisjointWithEmpty1\\#end,\\#begdisjointWithEmpty2\\#end,\\#begcreatedInHeapWithEmpty\\#end,\\#begcreatedInHeapWithSingleton\\#end,\\#begcreatedInHeapWithUnion\\#end,\\#begcreatedInHeapWithSetMinusFreshLocs\\#end,\\#begcreatedInHeapWithAllFields\\#end,\\#begcreatedInHeapWithArrayRange\\#end,\\#begreferencedObjectIsCreatedRight\\#end,\\#begreferencedObjectIsCreatedRightEQ\\#end,\\#begunionWithItself\\#end,\\#begintersectWithItself\\#end,\\#begsetMinusItself\\#end,\\#begsubsetOfItself\\#end,\\#begselectOfStore\\#end,\\#begselectOfCreate\\#end,\\#begselectOfAnon\\#end,\\#begselectOfMemset\\#end,\\#begonlyCreatedObjectsAreReferenced\\#end,\\#begonlyCreatedObjectsAreInLocSets\\#end,\\#begonlyCreatedObjectsAreInLocSetsEQ\\#end,\\#begarrayLengthNotNegative\\#end,\\#begwellFormedStoreObject\\#end,\\#begwellFormedStoreLocSet\\#end,\\#begwellFormedStorePrimitive\\#end,\\#begwellFormedCreate\\#end,\\#begwellFormedAnon\\#end,\\#begwellFormedMemsetObject\\#end,\\#begwellFormedMemsetLocSet\\#end,\\#begwellFormedMemsetPrimitive\\#end,\\#begselectOfStoreEQ\\#end,\\#begselectOfCreateEQ\\#end,\\#begselectOfAnonEQ\\#end,\\#begselectOfMemsetEQ\\#end,\\#begmemsetEmpty\\#end,\\#begselectCreatedOfAnonEQ\\#end,\\#begwellFormedStoreObjectEQ\\#end,\\#begwellFormedStoreLocSetEQ\\#end,\\#begwellFormedStorePrimitiveEQ\\#end,\\#begwellFormedAnonEQ\\#end,\\#begwellFormedMemsetObjectEQ\\#end,\\#begwellFormedMemsetPrimitiveEQ\\#end,\\#begaccDefinition\\#end,\\#begreachDefinition\\#end,\\#begreachZero\\#end,\\#begreachOne\\#end,\\#begreachNull\\#end,\\#begreachNull2\\#end,\\#begreachAddOne\\#end,\\#begreachAddOne2\\#end,\\#begreachUniquePathSameObject\\#end,\\#begreachDependenciesStoreSimple\\#end,\\#begreachDoesNotDependOnCreatedness\\#end,\\#begreachDependenciesStore\\#end,\\#begreachDependenciesAnon\\#end,\\#begreachDependenciesAnonCoarse\\#end,\\#begonly_created_objects_are_reachable\\#end,\\#begreach_does_not_depend_on_fresh_locs\\#end,\\#begreach_does_not_depend_on_fresh_locs_EQ\\#end -[StrategyProperty]METHOD_OPTIONS_KEY=METHOD_CONTRACT -[StrategyProperty]USER_TACLETS_OPTIONS_KEY3=USER_TACLETS_OFF -[StrategyProperty]SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY=SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER -[StrategyProperty]LOOP_OPTIONS_KEY=LOOP_NONE -[StrategyProperty]USER_TACLETS_OPTIONS_KEY2=USER_TACLETS_OFF -[StrategyProperty]USER_TACLETS_OPTIONS_KEY1=USER_TACLETS_OFF -[StrategyProperty]QUANTIFIERS_OPTIONS_KEY=QUANTIFIERS_NON_SPLITTING_WITH_PROGS -[StrategyProperty]NON_LIN_ARITH_OPTIONS_KEY=NON_LIN_ARITH_DEF_OPS -[SMTSettings]instantiateHierarchyAssumptions=true -[StrategyProperty]AUTO_INDUCTION_OPTIONS_KEY=AUTO_INDUCTION_OFF -[StrategyProperty]DEP_OPTIONS_KEY=DEP_ON -[StrategyProperty]BLOCK_OPTIONS_KEY=BLOCK_EXPAND -[StrategyProperty]CLASS_AXIOM_OPTIONS_KEY=CLASS_AXIOM_FREE -[StrategyProperty]SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY=SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF -[StrategyProperty]QUERY_NEW_OPTIONS_KEY=QUERY_OFF -[Strategy]Timeout=-1 -[Strategy]MaximumNumberOfAutomaticApplications=7000 -[SMTSettings]integersMaximum=2147483645 -[Choice]DefaultChoices=initialisation-initialisation\\:disableStaticInitialisation , wdChecks-wdChecks\\:off , reach-reach\\:on , moreSeqRules-moreSeqRules\\:off , sequences-sequences\\:on , Strings-Strings\\:on , mergeGenerateIsWeakeningGoal-mergeGenerateIsWeakeningGoal\\:on , runtimeExceptions-runtimeExceptions\\:ban , wdOperator-wdOperator\\:L , JavaCard-JavaCard\\:on , integerSimplificationRules-integerSimplificationRules\\:full , permissions-permissions\\:off , modelFields-modelFields\\:treatAsAxiom , assertions-assertions\\:safe , intRules-intRules\\:arithmeticSemanticsIgnoringOF , bigint-bigint\\:on , programRules-programRules\\:Java -[SMTSettings]useConstantsForBigOrSmallIntegers=true -[StrategyProperty]STOPMODE_OPTIONS_KEY=STOPMODE_DEFAULT -[StrategyProperty]QUERYAXIOM_OPTIONS_KEY=QUERYAXIOM_ON -[StrategyProperty]INF_FLOW_CHECK_PROPERTY=INF_FLOW_CHECK_FALSE -[SMTSettings]maxGenericSorts=2 -[SMTSettings]integersMinimum=-2147483645 -[SMTSettings]invariantForall=false -[SMTSettings]UseBuiltUniqueness=false -[SMTSettings]explicitTypeHierarchy=false -[Strategy]ActiveStrategy=JavaCardDLStrategy -[StrategyProperty]SPLITTING_OPTIONS_KEY=SPLITTING_DELAYED -" -} +\settings // Proof-Settings-Config-File +{ + "Choice" : { + "JavaCard" : "JavaCard:on", + "Strings" : "Strings:on", + "assertions" : "assertions:safe", + "bigint" : "bigint:on", + "finalFields" : "finalFields:immutable", + "floatRules" : "floatRules:strictfpOnly", + "initialisation" : "initialisation:disableStaticInitialisation", + "intRules" : "intRules:arithmeticSemanticsIgnoringOF", + "integerSimplificationRules" : "integerSimplificationRules:full", + "javaLoopTreatment" : "javaLoopTreatment:efficient", + "mergeGenerateIsWeakeningGoal" : "mergeGenerateIsWeakeningGoal:on", + "methodExpansion" : "methodExpansion:modularOnly", + "modelFields" : "modelFields:treatAsAxiom", + "moreSeqRules" : "moreSeqRules:off", + "permissions" : "permissions:off", + "programRules" : "programRules:Java", + "reach" : "reach:on", + "runtimeExceptions" : "runtimeExceptions:ban", + "sequences" : "sequences:on", + "soundDefaultContracts" : "soundDefaultContracts:on", + "wdChecks" : "wdChecks:off", + "wdOperator" : "wdOperator:L" + }, + "Labels" : { + "UseOriginLabels" : true + }, + "NewSMT" : { + + }, + "SMTSettings" : { + "SelectedTaclets" : [ + + ], + "UseBuiltUniqueness" : false, + "explicitTypeHierarchy" : false, + "instantiateHierarchyAssumptions" : true, + "integersMaximum" : 2147483645, + "integersMinimum" : -2147483645, + "invariantForall" : false, + "maxGenericSorts" : 2, + "useConstantsForBigOrSmallIntegers" : true, + "useUninterpretedMultiplication" : true + }, + "Strategy" : { + "ActiveStrategy" : "JavaCardDLStrategy", + "MaximumNumberOfAutomaticApplications" : 7000, + "Timeout" : -1, + "options" : { + "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", + "BLOCK_OPTIONS_KEY" : "BLOCK_EXPAND", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_FREE", + "DEP_OPTIONS_KEY" : "DEP_ON", + "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", + "LOOP_OPTIONS_KEY" : "LOOP_NONE", + "METHOD_OPTIONS_KEY" : "METHOD_CONTRACT", + "MPS_OPTIONS_KEY" : "MPS_MERGE", + "NON_LIN_ARITH_OPTIONS_KEY" : "NON_LIN_ARITH_DEF_OPS", + "OSS_OPTIONS_KEY" : "OSS_ON", + "QUANTIFIERS_OPTIONS_KEY" : "QUANTIFIERS_NON_SPLITTING_WITH_PROGS", + "QUERYAXIOM_OPTIONS_KEY" : "QUERYAXIOM_ON", + "QUERY_NEW_OPTIONS_KEY" : "QUERY_OFF", + "SPLITTING_OPTIONS_KEY" : "SPLITTING_DELAYED", + "STOPMODE_OPTIONS_KEY" : "STOPMODE_DEFAULT", + "SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER", + "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF", + "USER_TACLETS_OPTIONS_KEY1" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY2" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY3" : "USER_TACLETS_OFF", + "VBT_PHASE" : "VBT_SYM_EX" + } + } + } -\javaSource "."; -\proofObligation { - "name": "A[A::m(boolean)].JML operation contract.0", - "contract": "A[A::m(boolean)].JML operation contract.0", - "class": "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", +\javaSource ".";\proofObligation +// +{ + "class" : "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", + "contract" : "A[A::m(boolean)].JML operation contract.0", + "name" : "A[A::m(boolean)].JML operation contract.0" } \proof { -(keyLog "0" (keyUser "dscheurer" ) (keyVersion "327015b4b7182a877de25e71099a53d78919e1bb")) -(keyLog "1" (keyUser "dscheurer" ) (keyVersion "327015b4b7182a877de25e71099a53d78919e1bb")) +(keyLog "0" (keyUser "ulbrich" ) (keyVersion "ec5929d9d07ad15825a3df906130ec25331835c0")) -(autoModeTime "1166") +(autoModeTime "2175") (branch "dummy ID" -(builtin "One Step Simplification" (formula "1") (newnames "b,self,result,exc,heapAtPre,o,f")) + (builtin "One Step Simplification" (formula "1") (newnames "heapAtPre,o,f")) (rule "impRight" (formula "1")) (rule "andLeft" (formula "1")) (rule "andLeft" (formula "1")) @@ -63,45 +100,45 @@ (rule "andLeft" (formula "1")) (rule "notLeft" (formula "2")) (rule "assignment" (formula "7") (term "1")) -(builtin "One Step Simplification" (formula "7")) + (builtin "One Step Simplification" (formula "7")) (rule "Class_invariant_axiom_for_A" (formula "5") (ifseqformula "3")) (rule "true_left" (formula "5")) (rule "methodBodyExpand" (formula "6") (term "1") (newnames "heapBefore_m,savedHeapBefore_m")) -(builtin "One Step Simplification" (formula "6")) + (builtin "One Step Simplification" (formula "6")) (rule "variableDeclaration" (formula "6") (term "1") (newnames "r")) (rule "ifElseSplit" (formula "6")) (branch "if _b true" - (builtin "One Step Simplification" (formula "7")) - (builtin "One Step Simplification" (formula "1")) - (rule "compound_greater_than_comparison_1" (formula "7") (term "1") (inst "#v0=x")) + (builtin "One Step Simplification" (formula "7")) + (builtin "One Step Simplification" (formula "1")) + (rule "compound_greater_than_comparison_1" (formula "7") (term "1") (inst "#v0=i")) (rule "variableDeclarationAssign" (formula "7") (term "1")) - (rule "variableDeclaration" (formula "7") (term "1") (newnames "x")) - (builtin "Use Operation Contract" (formula "7") (newnames "heapBefore_f,result_0,exc_0,heapAfter_f,anon_heap_f") (contract "A[A::f()].JML operation contract.0")) + (rule "variableDeclaration" (formula "7") (term "1") (newnames "i")) + (builtin "Use Operation Contract" (formula "7") (newnames "heapBefore_f,result_f,exc_0,heapAfter_f,anon_heap_f") (contract "A[A::f()].JML operation contract.0") (modality "diamond")) (branch "Post (f)" - (builtin "One Step Simplification" (formula "7")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "7")) + (builtin "One Step Simplification" (formula "9")) (rule "andLeft" (formula "7")) (rule "andLeft" (formula "8")) (rule "typeEqDerived" (formula "9") (term "0,1,1,1,1") (ifseqformula "8")) (rule "typeEqDerived" (formula "9") (term "0,0,1,1,1") (ifseqformula "8")) - (builtin "One Step Simplification" (formula "9") (ifInst "" (formula "8")) (ifInst "" (formula "8"))) + (builtin "One Step Simplification" (formula "9") (ifInst "" (formula "8")) (ifInst "" (formula "8"))) (rule "andLeft" (formula "9")) (rule "inEqSimp_gtToGeq" (formula "9")) (rule "times_zero_1" (formula "9") (term "1,0,0")) (rule "add_zero_right" (formula "9") (term "0,0")) (rule "assignment" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) + (builtin "One Step Simplification" (formula "12")) (rule "inEqSimp_sepPosMonomial1" (formula "9")) (rule "mul_literals" (formula "9") (term "1")) (rule "Class_invariant_axiom_for_A" (formula "10") (ifseqformula "4")) (rule "true_left" (formula "10")) (rule "greater_than_comparison_simple" (formula "11") (term "1")) - (builtin "One Step Simplification" (formula "11")) + (builtin "One Step Simplification" (formula "11")) (rule "inEqSimp_gtToGeq" (formula "11") (term "0,0,1,0")) (rule "times_zero_1" (formula "11") (term "1,0,0,0,0,1,0")) - (rule "add_literals" (formula "11") (term "0,0,0,0,1,0")) + (rule "add_zero_right" (formula "11") (term "0,0,0,0,1,0")) (rule "blockEmpty" (formula "11") (term "1")) - (builtin "MergeRule" (formula "11") (mergeProc "MergeByIfThenElse") (nrMergePartners "1") (mergeId "196")) + (builtin "MergeRule" (formula "11") (mergeProc "MergeByIfThenElse") (nrMergePartners "1") (mergeId "199")) (rule "deleteMergePoint" (formula "7") (term "1")) (rule "notLeft" (formula "1")) (rule "inEqSimp_sepPosMonomial1" (formula "7") (term "0,1,0,0,0,0")) @@ -123,6 +160,7 @@ (rule "shift_paren_or" (formula "7")) (rule "commute_or" (formula "6") (term "0")) (rule "commute_and_2" (formula "5") (term "1")) + (rule "commute_and_2" (formula "5") (term "0,0")) (rule "cnf_rightDist" (formula "5")) (rule "andLeft" (formula "5")) (rule "cnf_rightDist" (formula "6")) @@ -142,9 +180,9 @@ (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) + (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) - (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) (rule "commute_or" (formula "6")) @@ -156,9 +194,9 @@ (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) + (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) - (rule "commute_or" (formula "7")) (rule "cnf_rightDist" (formula "6")) (rule "andLeft" (formula "6")) (rule "commute_or" (formula "6")) @@ -185,298 +223,216 @@ (rule "andLeft" (formula "5")) (rule "commute_or" (formula "6")) (rule "cnf_rightDist" (formula "9")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) (rule "cnf_rightDist" (formula "5")) (rule "andLeft" (formula "5")) (rule "methodCallReturn" (formula "35") (term "1")) (rule "assignment" (formula "35") (term "1")) - (builtin "One Step Simplification" (formula "35")) + (builtin "One Step Simplification" (formula "35")) (rule "methodCallEmpty" (formula "35") (term "1")) (rule "tryEmpty" (formula "35") (term "1")) (rule "emptyModality" (formula "35") (term "1")) - (builtin "One Step Simplification" (formula "35") (ifInst "" (formula "13"))) + (builtin "One Step Simplification" (formula "35") (ifInst "" (formula "13"))) (rule "Class_invariant_axiom_for_A" (formula "35") (ifseqformula "3")) (rule "closeTrue" (formula "35")) ) (branch "Exceptional Post (f)" - (builtin "One Step Simplification" (formula "7")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "7")) (rule "andLeft" (formula "7")) - (rule "selectCreatedOfAnonAsFormulaEQ" (formula "8") (term "1,0") (ifseqformula "7")) (rule "andLeft" (formula "8")) - (rule "andLeft" (formula "9")) (rule "andLeft" (formula "8")) + (rule "andLeft" (formula "10")) (rule "notLeft" (formula "8")) (rule "replace_known_right" (formula "9") (term "0") (ifseqformula "11")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) (rule "true_left" (formula "9")) (rule "replace_known_right" (formula "9") (term "0,0") (ifseqformula "10")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) (rule "andLeft" (formula "9")) (rule "blockThrow" (formula "13") (term "1")) + (rule "pullOutSelect" (formula "8") (term "0") (inst "selectSK=java_lang_Object_created__0")) + (rule "simplifySelectOfAnonEQ" (formula "8") (ifseqformula "7")) + (builtin "One Step Simplification" (formula "8") (ifInst "" (formula "12"))) + (rule "ifthenelse_negated" (formula "8") (term "0")) + (rule "applyEqRigid" (formula "8") (term "1") (ifseqformula "9")) + (rule "ifEqualsTRUE" (formula "8")) + (builtin "One Step Simplification" (formula "8")) + (rule "hideAuxiliaryEqConcrete" (formula "9")) (rule "Class_invariant_axiom_for_A" (formula "9") (ifseqformula "4")) (rule "true_left" (formula "9")) + (rule "cnf_rightDist" (formula "8")) + (builtin "One Step Simplification" (formula "8")) + (rule "commute_or" (formula "8")) (rule "methodCallParamThrow" (formula "12") (term "1")) (rule "tryCatchThrow" (formula "12") (term "1")) - (rule "ifElseUnfold" (formula "12") (term "1") (inst "#boolv=x")) - (rule "variableDeclaration" (formula "12") (term "1") (newnames "x_1")) - (rule "equality_comparison_simple" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) - (rule "replace_known_right" (formula "12") (term "0,0,1,0") (ifseqformula "10")) - (builtin "One Step Simplification" (formula "12")) - (rule "ifElseSplit" (formula "12")) - (branch "if x_1 true" - (builtin "One Step Simplification" (formula "13")) - (builtin "One Step Simplification" (formula "1")) + (branch "Null reference in throw" + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "11"))) (rule "closeFalse" (formula "1")) ) - (branch "if x_1 false" - (builtin "One Step Simplification" (formula "13")) - (builtin "One Step Simplification" (formula "1")) - (rule "true_left" (formula "1")) + (branch "Normal execution" + (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "10"))) + (rule "false_right" (formula "12")) (rule "ifElseSplit" (formula "12")) (branch "if exc_0 instanceof java.lang.Throwable true" - (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "11"))) + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "11"))) (rule "true_left" (formula "1")) (rule "variableDeclaration" (formula "12") (term "1") (newnames "e")) (rule "delete_unnecessary_cast" (formula "12") (term "1")) - (branch "Normal Execution (exc_0 instanceof java.lang.Throwable)" - (builtin "One Step Simplification" (formula "13")) - (builtin "One Step Simplification" (formula "1")) - (rule "true_left" (formula "1")) - (rule "assignment" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) - (rule "emptyModality" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "10")) (ifInst "" (formula "10")) (ifInst "" (formula "9"))) - (rule "Class_invariant_axiom_for_A" (formula "12") (ifseqformula "4")) - (rule "closeTrue" (formula "12")) - ) - (branch "ClassCastException (!(exc_0 instanceof java.lang.Throwable))" - (builtin "One Step Simplification" (formula "12")) - (rule "closeTrue" (formula "12")) - ) + (builtin "One Step Simplification" (formula "13")) + (builtin "One Step Simplification" (formula "1")) + (rule "true_left" (formula "1")) + (rule "assignment" (formula "12") (term "1")) + (builtin "One Step Simplification" (formula "12")) + (rule "emptyModality" (formula "12") (term "1")) + (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "10")) (ifInst "" (formula "10")) (ifInst "" (formula "9"))) + (rule "Class_invariant_axiom_for_A" (formula "12") (ifseqformula "4")) + (rule "closeTrue" (formula "12")) ) (branch "if exc_0 instanceof java.lang.Throwable false" - (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "11"))) + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "11"))) (rule "closeFalse" (formula "1")) ) ) ) (branch "Pre (f)" - (builtin "One Step Simplification" (formula "7") (ifInst "" (formula "2"))) + (builtin "One Step Simplification" (formula "7") (ifInst "" (formula "2"))) (rule "Class_invariant_axiom_for_A" (formula "7") (ifseqformula "4")) (rule "closeTrue" (formula "7")) ) ) (branch "if _b false" - (builtin "One Step Simplification" (formula "7")) - (builtin "One Step Simplification" (formula "1")) + (builtin "One Step Simplification" (formula "7")) + (builtin "One Step Simplification" (formula "1")) (rule "notLeft" (formula "1")) (rule "compound_inequality_comparison_1" (formula "7") (term "1") (inst "#v0=o")) (rule "variableDeclarationAssign" (formula "7") (term "1")) (rule "variableDeclaration" (formula "7") (term "1") (newnames "o")) - (builtin "Use Operation Contract" (formula "7") (newnames "heapBefore_g,result_0,exc_0,heapAfter_g,anon_heap_g") (contract "A[A::g()].JML operation contract.0")) + (builtin "Use Operation Contract" (formula "7") (newnames "heapBefore_g,result_g,exc_0,heapAfter_g,anon_heap_g") (contract "A[A::g()].JML operation contract.0") (modality "diamond")) (branch "Post (g)" - (builtin "One Step Simplification" (formula "6")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "6")) (rule "andLeft" (formula "6")) - (rule "selectCreatedOfAnonAsFormulaEQ" (formula "7") (term "1,1,0") (ifseqformula "6")) (rule "andLeft" (formula "7")) - (rule "andLeft" (formula "8")) (rule "andLeft" (formula "7")) - (rule "typeEqDerived" (formula "10") (term "0,0,1,1") (ifseqformula "7")) - (rule "typeEqDerived" (formula "10") (term "0,1,1,1") (ifseqformula "7")) - (builtin "One Step Simplification" (formula "10") (ifInst "" (formula "7"))) - (rule "true_left" (formula "10")) - (rule "replace_known_left" (formula "9") (term "0") (ifseqformula "7")) - (builtin "One Step Simplification" (formula "9")) + (rule "typeEqDerived" (formula "9") (term "0,0,1,1,1") (ifseqformula "7")) + (rule "typeEqDerived" (formula "9") (term "0,1,1,1,1") (ifseqformula "7")) + (builtin "One Step Simplification" (formula "9") (ifInst "" (formula "7")) (ifInst "" (formula "7"))) (rule "andLeft" (formula "9")) - (rule "andLeft" (formula "10")) (rule "notLeft" (formula "9")) + (rule "andLeft" (formula "9")) + (rule "notLeft" (formula "10")) (rule "replace_known_right" (formula "8") (term "0") (ifseqformula "10")) - (builtin "One Step Simplification" (formula "8")) + (builtin "One Step Simplification" (formula "8")) (rule "assignment" (formula "13") (term "1")) - (builtin "One Step Simplification" (formula "13")) + (builtin "One Step Simplification" (formula "13")) + (rule "pullOutSelect" (formula "8") (term "0") (inst "selectSK=java_lang_Object_created__0")) + (rule "simplifySelectOfAnonEQ" (formula "8") (ifseqformula "6")) + (builtin "One Step Simplification" (formula "8") (ifInst "" (formula "11"))) + (rule "ifthenelse_negated" (formula "8") (term "0")) + (rule "applyEq" (formula "8") (term "1") (ifseqformula "9")) + (rule "ifEqualsTRUE" (formula "8")) + (builtin "One Step Simplification" (formula "8")) + (rule "hideAuxiliaryEqConcrete" (formula "9")) (rule "Class_invariant_axiom_for_A" (formula "9") (ifseqformula "3")) (rule "true_left" (formula "9")) + (rule "cnf_rightDist" (formula "8")) + (builtin "One Step Simplification" (formula "8")) + (rule "commute_or" (formula "8")) (rule "inequality_comparison_simple" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) + (builtin "One Step Simplification" (formula "12")) (rule "replace_known_right" (formula "12") (term "0,0,1,0") (ifseqformula "9")) - (builtin "One Step Simplification" (formula "12")) + (builtin "One Step Simplification" (formula "12")) (rule "blockEmpty" (formula "12") (term "1")) - (builtin "CloseAfterMerge" (formula "12") (newnames "result_0_0,result_0_1,exc_0_0,exc_0_1,P") (mergeNode "196")) + (builtin "CloseAfterMerge" (formula "12") (newnames "exc_0_0,exc_0_1,P") (mergeNode "199")) (branch "Merged node is weakening" - (rule "cut" (inst "cutFormula= !self = null - & ( !b = TRUE - & ( !result_0_1 = null - & ( wellFormed(heap) - & ( boolean::select(heap, - self, - java.lang.Object::) - = TRUE - & ( A::exactInstance(self) = TRUE - & ( measuredByEmpty - & ( wellFormed(anon_heap_g<>) - & ( anon(heap, - allLocs, - anon_heap_g<>) - = heapAfter_g - & ( exc_0_1 = null - & ( boolean::select(heap, - result_0_1, - java.lang.Object::) - = TRUE - | boolean::select(anon_heap_g<>, - result_0_1, - java.lang.Object::) - = TRUE)))))))))) --> !self = null - & wellFormed(heap) - & boolean::select(heap, - self, - java.lang.Object::) - = TRUE - & A::exactInstance(self) = TRUE - & measuredByEmpty - & ( b = TRUE - & wellFormed(anon_heap_f<>) - & anon(heap, - allLocs, - anon_heap_f<>) - = heapAfter_f - & exc_0_0 = null - & geq(result_0_0, Z(1(#))) - | !b = TRUE - & !result_0_1 = null - & wellFormed(anon_heap_g<>) - & anon(heap, - allLocs, - anon_heap_g<>) - = heapAfter_g - & exc_0_1 = null - & ( boolean::select(heap, - result_0_1, - java.lang.Object::) - = TRUE - | boolean::select(anon_heap_g<>, - result_0_1, - java.lang.Object::) - = TRUE))") (userinteraction)) - (branch "CUT: !self = null & ( !b = TRUE & ( !result_0_1 = null & ( wellFormed(heap) & ( self. = TRUE & ( A::exactInstance(self) = TRUE & ( measuredByEmpty & ( wellFormed(anon_heap_g<>) & ( heap[anon(allLocs, anon_heap_g<>)] = heapAfter_g & ( exc_0_1 = null & ( result_0_1. = TRUE | result_0_1.@anon_heap_g<> = TRUE)))))))))) -> !self = null & wellFormed(heap) & self. = TRUE & A::exactInstance(self) = TRUE & measuredByEmpty & ( b = TRUE & wellFormed(anon_heap_f<>) & heap[anon(allLocs, anon_heap_f<>)] = heapAfter_f & exc_0_0 = null & result_0_0 >= 1 | !b = TRUE & !result_0_1 = null & wellFormed(anon_heap_g<>) & heap[anon(allLocs, anon_heap_g<>)] = heapAfter_g & exc_0_1 = null & ( result_0_1. = TRUE | result_0_1.@anon_heap_g<> = TRUE)) TRUE" - (builtin "One Step Simplification" (formula "2")) - (rule "impRight" (formula "2")) - (rule "impRight" (formula "3")) - (rule "andLeft" (formula "1")) - (rule "andLeft" (formula "2")) - (rule "notLeft" (formula "1")) - (rule "notLeft" (formula "1")) - (rule "andLeft" (formula "1")) - (rule "andLeft" (formula "2")) - (rule "notLeft" (formula "1")) - (rule "andLeft" (formula "2")) - (rule "andLeft" (formula "3")) - (rule "andLeft" (formula "4")) - (rule "andLeft" (formula "5")) - (rule "andLeft" (formula "6")) - (rule "andLeft" (formula "7")) - (rule "replace_known_left" (formula "9") (term "1,0,0,0") (ifseqformula "3")) - (builtin "One Step Simplification" (formula "9") (ifInst "" (formula "13")) (ifInst "" (formula "1")) (ifInst "" (formula "2")) (ifInst "" (formula "4")) (ifInst "" (formula "12")) (ifInst "" (formula "12")) (ifInst "" (formula "11")) (ifInst "" (formula "5")) (ifInst "" (formula "6")) (ifInst "" (formula "7")) (ifInst "" (formula "8")) (ifInst "" (formula "12")) (ifInst "" (formula "12")) (ifInst "" (formula "14"))) - (rule "closeFalse" (formula "9")) - ) - (branch "CUT: !self = null & ( !b = TRUE & ( !result_0_1 = null & ( wellFormed(heap) & ( self. = TRUE & ( A::exactInstance(self) = TRUE & ( measuredByEmpty & ( wellFormed(anon_heap_g<>) & ( heap[anon(allLocs, anon_heap_g<>)] = heapAfter_g & ( exc_0_1 = null & ( result_0_1. = TRUE | result_0_1.@anon_heap_g<> = TRUE)))))))))) -> !self = null & wellFormed(heap) & self. = TRUE & A::exactInstance(self) = TRUE & measuredByEmpty & ( b = TRUE & wellFormed(anon_heap_f<>) & heap[anon(allLocs, anon_heap_f<>)] = heapAfter_f & exc_0_0 = null & result_0_0 >= 1 | !b = TRUE & !result_0_1 = null & wellFormed(anon_heap_g<>) & heap[anon(allLocs, anon_heap_g<>)] = heapAfter_g & exc_0_1 = null & ( result_0_1. = TRUE | result_0_1.@anon_heap_g<> = TRUE)) FALSE" - (rule "hide_right" (formula "2") (userinteraction)) - (rule "impRight" (formula "1")) - (rule "andLeft" (formula "1")) - (rule "notLeft" (formula "1")) - (rule "andLeft" (formula "1")) - (rule "notLeft" (formula "1")) - (rule "andLeft" (formula "1")) - (rule "andLeft" (formula "2")) - (rule "notLeft" (formula "1")) - (rule "andLeft" (formula "2")) - (rule "andLeft" (formula "3")) - (rule "andLeft" (formula "4")) - (rule "andLeft" (formula "5")) - (rule "andLeft" (formula "6")) - (rule "andLeft" (formula "7")) - (rule "replace_known_left" (formula "12") (term "1,0") (ifseqformula "4")) - (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "11")) (ifInst "" (formula "1")) (ifInst "" (formula "2")) (ifInst "" (formula "3")) (ifInst "" (formula "10")) (ifInst "" (formula "10")) (ifInst "" (formula "9")) (ifInst "" (formula "5")) (ifInst "" (formula "6")) (ifInst "" (formula "7")) (ifInst "" (formula "8"))) - (rule "closeTrue" (formula "12")) - ) + (builtin "One Step Simplification" (formula "1")) + (rule "impRight" (formula "1")) + (rule "impRight" (formula "2")) + (rule "andLeft" (formula "1")) + (rule "notLeft" (formula "1")) + (rule "andLeft" (formula "1")) + (rule "notLeft" (formula "1")) + (rule "andLeft" (formula "1")) + (rule "andLeft" (formula "2")) + (rule "notLeft" (formula "1")) + (rule "andLeft" (formula "2")) + (rule "andLeft" (formula "3")) + (rule "andLeft" (formula "4")) + (rule "andLeft" (formula "5")) + (rule "andLeft" (formula "6")) + (rule "andLeft" (formula "7")) + (rule "replace_known_right" (formula "9") (term "0,6,1") (ifseqformula "11")) + (builtin "One Step Simplification" (formula "9") (ifInst "" (formula "12")) (ifInst "" (formula "1")) (ifInst "" (formula "2")) (ifInst "" (formula "3")) (ifInst "" (formula "4")) (ifInst "" (formula "11")) (ifInst "" (formula "11")) (ifInst "" (formula "10")) (ifInst "" (formula "5")) (ifInst "" (formula "6")) (ifInst "" (formula "7")) (ifInst "" (formula "8")) (ifInst "" (formula "11")) (ifInst "" (formula "13"))) + (rule "closeFalse" (formula "9")) ) - (branch "Merged with node 196" + (branch "Merged with node 199" ) ) (branch "Exceptional Post (g)" - (builtin "One Step Simplification" (formula "6")) - (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "9")) + (builtin "One Step Simplification" (formula "6")) (rule "andLeft" (formula "6")) - (rule "selectCreatedOfAnonAsFormulaEQ" (formula "7") (term "1,0") (ifseqformula "6")) (rule "andLeft" (formula "7")) (rule "andLeft" (formula "7")) (rule "andLeft" (formula "9")) (rule "notLeft" (formula "7")) (rule "replace_known_right" (formula "8") (term "0") (ifseqformula "10")) - (builtin "One Step Simplification" (formula "8")) + (builtin "One Step Simplification" (formula "8")) (rule "true_left" (formula "8")) (rule "replace_known_right" (formula "8") (term "0,0") (ifseqformula "9")) - (builtin "One Step Simplification" (formula "8")) + (builtin "One Step Simplification" (formula "8")) (rule "andLeft" (formula "8")) (rule "blockThrow" (formula "13") (term "1")) + (rule "pullOutSelect" (formula "7") (term "0") (inst "selectSK=java_lang_Object_created__0")) + (rule "simplifySelectOfAnonEQ" (formula "7") (ifseqformula "6")) + (builtin "One Step Simplification" (formula "7") (ifInst "" (formula "11"))) + (rule "ifthenelse_negated" (formula "7") (term "0")) + (rule "applyEq" (formula "7") (term "1") (ifseqformula "8")) + (rule "ifEqualsTRUE" (formula "7")) + (builtin "One Step Simplification" (formula "7")) + (rule "hideAuxiliaryEqConcrete" (formula "8")) (rule "Class_invariant_axiom_for_A" (formula "8") (ifseqformula "3")) (rule "true_left" (formula "8")) + (rule "cnf_rightDist" (formula "7")) + (builtin "One Step Simplification" (formula "7")) + (rule "commute_or" (formula "7")) (rule "methodCallParamThrow" (formula "12") (term "1")) (rule "tryCatchThrow" (formula "12") (term "1")) - (rule "ifElseUnfold" (formula "12") (term "1") (inst "#boolv=x")) - (rule "variableDeclaration" (formula "12") (term "1") (newnames "x")) - (rule "equality_comparison_simple" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) - (rule "replace_known_right" (formula "12") (term "0,0,1,0") (ifseqformula "9")) - (builtin "One Step Simplification" (formula "12")) - (rule "ifElseSplit" (formula "12")) - (branch "if x true" - (builtin "One Step Simplification" (formula "13")) - (builtin "One Step Simplification" (formula "1")) + (branch "Null reference in throw" + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "10"))) (rule "closeFalse" (formula "1")) ) - (branch "if x false" - (builtin "One Step Simplification" (formula "13")) - (builtin "One Step Simplification" (formula "1")) - (rule "true_left" (formula "1")) + (branch "Normal execution" + (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "9"))) + (rule "false_right" (formula "12")) (rule "ifElseSplit" (formula "12")) (branch "if exc_0 instanceof java.lang.Throwable true" - (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "10"))) + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "10"))) (rule "true_left" (formula "1")) (rule "variableDeclaration" (formula "12") (term "1") (newnames "e")) (rule "delete_unnecessary_cast" (formula "12") (term "1")) - (branch "Normal Execution (exc_0 instanceof java.lang.Throwable)" - (builtin "One Step Simplification" (formula "1")) - (builtin "One Step Simplification" (formula "13")) - (rule "true_left" (formula "1")) - (rule "assignment" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12")) - (rule "emptyModality" (formula "12") (term "1")) - (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "9")) (ifInst "" (formula "9")) (ifInst "" (formula "8"))) - (rule "Class_invariant_axiom_for_A" (formula "12") (ifseqformula "3")) - (rule "closeTrue" (formula "12")) - ) - (branch "ClassCastException (!(exc_0 instanceof java.lang.Throwable))" - (builtin "One Step Simplification" (formula "12")) - (rule "closeTrue" (formula "12")) - ) + (builtin "One Step Simplification" (formula "13")) + (builtin "One Step Simplification" (formula "1")) + (rule "true_left" (formula "1")) + (rule "assignment" (formula "12") (term "1")) + (builtin "One Step Simplification" (formula "12")) + (rule "emptyModality" (formula "12") (term "1")) + (builtin "One Step Simplification" (formula "12") (ifInst "" (formula "9")) (ifInst "" (formula "9")) (ifInst "" (formula "8"))) + (rule "Class_invariant_axiom_for_A" (formula "12") (ifseqformula "3")) + (rule "closeTrue" (formula "12")) ) (branch "if exc_0 instanceof java.lang.Throwable false" - (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "10"))) + (builtin "One Step Simplification" (formula "1") (ifInst "" (formula "10"))) (rule "closeFalse" (formula "1")) ) ) ) (branch "Pre (g)" - (builtin "One Step Simplification" (formula "7") (ifInst "" (formula "1"))) + (builtin "One Step Simplification" (formula "7") (ifInst "" (formula "1"))) (rule "Class_invariant_axiom_for_A" (formula "7") (ifseqformula "3")) (rule "closeTrue" (formula "7")) ) ) ) - } diff --git a/key.ncore.calculus/src/main/java/org/key_project/prover/sequent/Semisequent.java b/key.ncore.calculus/src/main/java/org/key_project/prover/sequent/Semisequent.java index 88b152e1c46..64a099b7bd7 100644 --- a/key.ncore.calculus/src/main/java/org/key_project/prover/sequent/Semisequent.java +++ b/key.ncore.calculus/src/main/java/org/key_project/prover/sequent/Semisequent.java @@ -190,6 +190,12 @@ public SequentFormula getFirst() { return seqList.head(); } + /// @return the last [SequentFormula] of this [Semisequent] + public SequentFormula getLast() { + return seqList.last(); + // or return seqList.take(seqList.size() - 1).head(); + } + /// Returns iterator about the formulas contained in this [Semisequent] /// /// @return iterator about the formulas contained in this [Semisequent] diff --git a/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java b/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java index b705e697bf2..326e1697c63 100644 --- a/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java +++ b/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java @@ -82,6 +82,13 @@ public static PosInTerm parseReverseString(String s) { : new PosInTerm(positions, (char) positions.length, true); } + public PosInTerm prepend(char index) { + var newPositions = new char[size + 1]; + System.arraycopy(positions, 0, newPositions, 1, size); + newPositions[0] = index; + return new PosInTerm(newPositions, (char) (size + 1), false); + } + /// returns the instance representing the top level position /// /// @return the top level position @@ -130,6 +137,25 @@ public PosInTerm firstN(int n) { return new PosInTerm(positions, (char) n, true); } + + /// returns the position of the suffix of length n + /// @param n the length of the suffix + /// @return the suffix of this position of length n + /// @throws IndexOutOfBoundsException if n is greater than the depth of this + /// position + public PosInTerm lastN(int n) { + if (n > size) { + throw new IndexOutOfBoundsException("Position is shorter than " + n); + } else if (n == 0) { + return getTopLevel(); + } else if (n == size) { + return this; + } + final char[] newPositions = new char[n]; + System.arraycopy(positions, size - n, newPositions, 0, n); + return new PosInTerm(newPositions, (char) n, false); + } + /// returns the position for the i-th subterm of the subterm described by this /// position /// @@ -237,7 +263,7 @@ public boolean equals(@Nullable Object o) { /// /// @param it the iterator /// @return the String with the list of integers - public String integerList(IntIterator it) { + public static String integerList(IntIterator it) { final StringBuilder list = new StringBuilder("["); while (it.hasNext()) { list.append(it.next()); diff --git a/key.ui/examples/heap/BoyerMoore/BM.bm.key b/key.ui/examples/heap/BoyerMoore/BM.bm.key new file mode 100644 index 00000000000..d9bd59e4d27 --- /dev/null +++ b/key.ui/examples/heap/BoyerMoore/BM.bm.key @@ -0,0 +1,86 @@ +\profile "Java Profile"; + +\settings // Proof-Settings-Config-File +{ + "Choice" : { + "JavaCard" : "JavaCard:on", + "Strings" : "Strings:on", + "assertions" : "assertions:on", + "bigint" : "bigint:on", + "floatRules" : "floatRules:strictfpOnly", + "initialisation" : "initialisation:disableStaticInitialisation", + "intRules" : "intRules:arithmeticSemanticsIgnoringOF", + "integerSimplificationRules" : "integerSimplificationRules:full", + "javaLoopTreatment" : "javaLoopTreatment:efficient", + "mergeGenerateIsWeakeningGoal" : "mergeGenerateIsWeakeningGoal:off", + "methodExpansion" : "methodExpansion:modularOnly", + "modelFields" : "modelFields:treatAsAxiom", + "moreSeqRules" : "moreSeqRules:off", + "permissions" : "permissions:off", + "programRules" : "programRules:Java", + "reach" : "reach:on", + "runtimeExceptions" : "runtimeExceptions:ban", + "sequences" : "sequences:on", + "wdChecks" : "wdChecks:off", + "wdOperator" : "wdOperator:L" + }, + "Labels" : { + "UseOriginLabels" : true + }, + "NewSMT" : { + + }, + "SMTSettings" : { + "SelectedTaclets" : [ + + ], + "UseBuiltUniqueness" : false, + "explicitTypeHierarchy" : false, + "instantiateHierarchyAssumptions" : true, + "integersMaximum" : 2147483645, + "integersMinimum" : -2147483645, + "invariantForall" : false, + "maxGenericSorts" : 2, + "useConstantsForBigOrSmallIntegers" : true, + "useUninterpretedMultiplication" : true + }, + "Strategy" : { + "ActiveStrategy" : "JavaCardDLStrategy", + "MaximumNumberOfAutomaticApplications" : 10000, + "Timeout" : -1, + "options" : { + "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", + "BLOCK_OPTIONS_KEY" : "BLOCK_CONTRACT_INTERNAL", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_DELAYED", + "DEP_OPTIONS_KEY" : "DEP_ON", + "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", + "LOOP_OPTIONS_KEY" : "LOOP_INVARIANT", + "METHOD_OPTIONS_KEY" : "METHOD_CONTRACT", + "MPS_OPTIONS_KEY" : "MPS_MERGE", + "NON_LIN_ARITH_OPTIONS_KEY" : "NON_LIN_ARITH_DEF_OPS", + "OSS_OPTIONS_KEY" : "OSS_ON", + "QUANTIFIERS_OPTIONS_KEY" : "QUANTIFIERS_NON_SPLITTING_WITH_PROGS", + "QUERYAXIOM_OPTIONS_KEY" : "QUERYAXIOM_ON", + "QUERY_NEW_OPTIONS_KEY" : "QUERY_OFF", + "SPLITTING_OPTIONS_KEY" : "SPLITTING_DELAYED", + "STOPMODE_OPTIONS_KEY" : "STOPMODE_DEFAULT", + "SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER", + "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF", + "USER_TACLETS_OPTIONS_KEY1" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY2" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY3" : "USER_TACLETS_OFF", + "VBT_PHASE" : "VBT_SYM_EX" + } + } + } + + +\javaSource "src"; + +\proofObligation { + "class" : "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", + "contract" : "BoyerMoore[BoyerMoore::bm([I)].JML normal_behavior operation contract.0", + "name" : "BoyerMoore[BoyerMoore::bm([I)].JML normal_behavior operation contract.0" + } + +\proofScript { macro "script-auto"; } diff --git a/key.ui/examples/heap/BoyerMoore/BM.count.accessible.key b/key.ui/examples/heap/BoyerMoore/BM.count.accessible.key new file mode 100644 index 00000000000..0c3fdb72aa0 --- /dev/null +++ b/key.ui/examples/heap/BoyerMoore/BM.count.accessible.key @@ -0,0 +1,85 @@ +\profile "Java Profile"; + +\settings // Proof-Settings-Config-File +{ + "Choice" : { + "JavaCard" : "JavaCard:on", + "Strings" : "Strings:on", + "assertions" : "assertions:on", + "bigint" : "bigint:on", + "floatRules" : "floatRules:strictfpOnly", + "initialisation" : "initialisation:disableStaticInitialisation", + "intRules" : "intRules:arithmeticSemanticsIgnoringOF", + "integerSimplificationRules" : "integerSimplificationRules:full", + "javaLoopTreatment" : "javaLoopTreatment:efficient", + "mergeGenerateIsWeakeningGoal" : "mergeGenerateIsWeakeningGoal:off", + "methodExpansion" : "methodExpansion:modularOnly", + "modelFields" : "modelFields:treatAsAxiom", + "moreSeqRules" : "moreSeqRules:off", + "permissions" : "permissions:off", + "programRules" : "programRules:Java", + "reach" : "reach:on", + "runtimeExceptions" : "runtimeExceptions:ban", + "sequences" : "sequences:on", + "wdChecks" : "wdChecks:off", + "wdOperator" : "wdOperator:L" + }, + "Labels" : { + "UseOriginLabels" : true + }, + "NewSMT" : { + + }, + "SMTSettings" : { + "SelectedTaclets" : [ + + ], + "UseBuiltUniqueness" : false, + "explicitTypeHierarchy" : false, + "instantiateHierarchyAssumptions" : true, + "integersMaximum" : 2147483645, + "integersMinimum" : -2147483645, + "invariantForall" : false, + "maxGenericSorts" : 2, + "useConstantsForBigOrSmallIntegers" : true, + "useUninterpretedMultiplication" : true + }, + "Strategy" : { + "ActiveStrategy" : "JavaCardDLStrategy", + "MaximumNumberOfAutomaticApplications" : 10000, + "Timeout" : -1, + "options" : { + "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", + "BLOCK_OPTIONS_KEY" : "BLOCK_CONTRACT_INTERNAL", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_DELAYED", + "DEP_OPTIONS_KEY" : "DEP_ON", + "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", + "LOOP_OPTIONS_KEY" : "LOOP_INVARIANT", + "METHOD_OPTIONS_KEY" : "METHOD_CONTRACT", + "MPS_OPTIONS_KEY" : "MPS_MERGE", + "NON_LIN_ARITH_OPTIONS_KEY" : "NON_LIN_ARITH_DEF_OPS", + "OSS_OPTIONS_KEY" : "OSS_ON", + "QUANTIFIERS_OPTIONS_KEY" : "QUANTIFIERS_NON_SPLITTING_WITH_PROGS", + "QUERYAXIOM_OPTIONS_KEY" : "QUERYAXIOM_ON", + "QUERY_NEW_OPTIONS_KEY" : "QUERY_OFF", + "SPLITTING_OPTIONS_KEY" : "SPLITTING_DELAYED", + "STOPMODE_OPTIONS_KEY" : "STOPMODE_DEFAULT", + "SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER", + "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF", + "USER_TACLETS_OPTIONS_KEY1" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY2" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY3" : "USER_TACLETS_OFF", + "VBT_PHASE" : "VBT_SYM_EX" + } + } + } + + +\javaSource "src"; + +\proofObligation { + "class" : "de.uka.ilkd.key.proof.init.DependencyContractPO", + "contract" : "BoyerMoore[BoyerMoore::count([I,\bigint,\bigint)].JML accessible clause.0", + "name" : "BoyerMoore[BoyerMoore::count([I,\bigint,\bigint)].JML accessible clause.0" + } + diff --git a/key.ui/examples/heap/BoyerMoore/BM.count.key b/key.ui/examples/heap/BoyerMoore/BM.count.key new file mode 100644 index 00000000000..52ef9b4383b --- /dev/null +++ b/key.ui/examples/heap/BoyerMoore/BM.count.key @@ -0,0 +1,84 @@ +\profile "Java Profile"; + +\settings // Proof-Settings-Config-File +{ + "Choice" : { + "JavaCard" : "JavaCard:on", + "Strings" : "Strings:on", + "assertions" : "assertions:on", + "bigint" : "bigint:on", + "floatRules" : "floatRules:strictfpOnly", + "initialisation" : "initialisation:disableStaticInitialisation", + "intRules" : "intRules:arithmeticSemanticsIgnoringOF", + "integerSimplificationRules" : "integerSimplificationRules:full", + "javaLoopTreatment" : "javaLoopTreatment:efficient", + "mergeGenerateIsWeakeningGoal" : "mergeGenerateIsWeakeningGoal:off", + "methodExpansion" : "methodExpansion:modularOnly", + "modelFields" : "modelFields:treatAsAxiom", + "moreSeqRules" : "moreSeqRules:off", + "permissions" : "permissions:off", + "programRules" : "programRules:Java", + "reach" : "reach:on", + "runtimeExceptions" : "runtimeExceptions:ban", + "sequences" : "sequences:on", + "wdChecks" : "wdChecks:off", + "wdOperator" : "wdOperator:L" + }, + "Labels" : { + "UseOriginLabels" : true + }, + "NewSMT" : { + + }, + "SMTSettings" : { + "SelectedTaclets" : [ + + ], + "UseBuiltUniqueness" : false, + "explicitTypeHierarchy" : false, + "instantiateHierarchyAssumptions" : true, + "integersMaximum" : 2147483645, + "integersMinimum" : -2147483645, + "invariantForall" : false, + "maxGenericSorts" : 2, + "useConstantsForBigOrSmallIntegers" : true, + "useUninterpretedMultiplication" : true + }, + "Strategy" : { + "ActiveStrategy" : "JavaCardDLStrategy", + "MaximumNumberOfAutomaticApplications" : 10000, + "Timeout" : -1, + "options" : { + "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", + "BLOCK_OPTIONS_KEY" : "BLOCK_CONTRACT_INTERNAL", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_DELAYED", + "DEP_OPTIONS_KEY" : "DEP_ON", + "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", + "LOOP_OPTIONS_KEY" : "LOOP_INVARIANT", + "METHOD_OPTIONS_KEY" : "METHOD_CONTRACT", + "MPS_OPTIONS_KEY" : "MPS_MERGE", + "NON_LIN_ARITH_OPTIONS_KEY" : "NON_LIN_ARITH_DEF_OPS", + "OSS_OPTIONS_KEY" : "OSS_ON", + "QUANTIFIERS_OPTIONS_KEY" : "QUANTIFIERS_NON_SPLITTING_WITH_PROGS", + "QUERYAXIOM_OPTIONS_KEY" : "QUERYAXIOM_ON", + "QUERY_NEW_OPTIONS_KEY" : "QUERY_OFF", + "SPLITTING_OPTIONS_KEY" : "SPLITTING_DELAYED", + "STOPMODE_OPTIONS_KEY" : "STOPMODE_DEFAULT", + "SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER", + "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF", + "USER_TACLETS_OPTIONS_KEY1" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY2" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY3" : "USER_TACLETS_OFF", + "VBT_PHASE" : "VBT_SYM_EX" + } + } + } + + +\javaSource "src"; + +\proofObligation { + "class" : "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", + "contract" : "BoyerMoore[BoyerMoore::count([I,\bigint,\bigint)].JML model_behavior operation contract.0", + "name" : "BoyerMoore[BoyerMoore::count([I,\bigint,\bigint)].JML model_behavior operation contract.0" + } diff --git a/key.ui/examples/heap/BoyerMoore/BM.monoLemma.key b/key.ui/examples/heap/BoyerMoore/BM.monoLemma.key new file mode 100644 index 00000000000..e4d85019478 --- /dev/null +++ b/key.ui/examples/heap/BoyerMoore/BM.monoLemma.key @@ -0,0 +1,83 @@ +\profile "Java Profile"; + +\settings // Proof-Settings-Config-File +{ + "Choice" : { + "JavaCard" : "JavaCard:on", + "Strings" : "Strings:on", + "assertions" : "assertions:on", + "bigint" : "bigint:on", + "floatRules" : "floatRules:strictfpOnly", + "initialisation" : "initialisation:disableStaticInitialisation", + "intRules" : "intRules:arithmeticSemanticsIgnoringOF", + "integerSimplificationRules" : "integerSimplificationRules:full", + "javaLoopTreatment" : "javaLoopTreatment:efficient", + "mergeGenerateIsWeakeningGoal" : "mergeGenerateIsWeakeningGoal:off", + "methodExpansion" : "methodExpansion:modularOnly", + "modelFields" : "modelFields:treatAsAxiom", + "moreSeqRules" : "moreSeqRules:off", + "permissions" : "permissions:off", + "programRules" : "programRules:Java", + "reach" : "reach:on", + "runtimeExceptions" : "runtimeExceptions:ban", + "sequences" : "sequences:on", + "wdChecks" : "wdChecks:off", + "wdOperator" : "wdOperator:L" + }, + "Labels" : { + "UseOriginLabels" : true + }, + "NewSMT" : { + + }, + "SMTSettings" : { + "SelectedTaclets" : [ + + ], + "UseBuiltUniqueness" : false, + "explicitTypeHierarchy" : false, + "instantiateHierarchyAssumptions" : true, + "integersMaximum" : 2147483645, + "integersMinimum" : -2147483645, + "invariantForall" : false, + "maxGenericSorts" : 2, + "useConstantsForBigOrSmallIntegers" : true, + "useUninterpretedMultiplication" : true + }, + "Strategy" : { + "ActiveStrategy" : "JavaCardDLStrategy", + "MaximumNumberOfAutomaticApplications" : 10000, + "Timeout" : -1, + "options" : { + "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", + "BLOCK_OPTIONS_KEY" : "BLOCK_CONTRACT_INTERNAL", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_DELAYED", + "DEP_OPTIONS_KEY" : "DEP_ON", + "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", + "LOOP_OPTIONS_KEY" : "LOOP_INVARIANT", + "METHOD_OPTIONS_KEY" : "METHOD_CONTRACT", + "MPS_OPTIONS_KEY" : "MPS_MERGE", + "NON_LIN_ARITH_OPTIONS_KEY" : "NON_LIN_ARITH_DEF_OPS", + "OSS_OPTIONS_KEY" : "OSS_ON", + "QUANTIFIERS_OPTIONS_KEY" : "QUANTIFIERS_NON_SPLITTING_WITH_PROGS", + "QUERYAXIOM_OPTIONS_KEY" : "QUERYAXIOM_ON", + "QUERY_NEW_OPTIONS_KEY" : "QUERY_OFF", + "SPLITTING_OPTIONS_KEY" : "SPLITTING_DELAYED", + "STOPMODE_OPTIONS_KEY" : "STOPMODE_DEFAULT", + "SYMBOLIC_EXECUTION_ALIAS_CHECK_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_ALIAS_CHECK_NEVER", + "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OPTIONS_KEY" : "SYMBOLIC_EXECUTION_NON_EXECUTION_BRANCH_HIDING_OFF", + "USER_TACLETS_OPTIONS_KEY1" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY2" : "USER_TACLETS_OFF", + "USER_TACLETS_OPTIONS_KEY3" : "USER_TACLETS_OFF", + "VBT_PHASE" : "VBT_SYM_EX" + } + } + } + +\javaSource "src"; + +\proofObligation { + "class" : "de.uka.ilkd.key.proof.init.FunctionalOperationContractPO", + "contract" : "BoyerMoore[BoyerMoore::monoLemma([I,int,int)].JML normal_behavior operation contract.0", + "name" : "BoyerMoore[BoyerMoore::monoLemma([I,int,int)].JML normal_behavior operation contract.0" + } diff --git a/key.ui/examples/heap/BoyerMoore/BoyerMoore.key b/key.ui/examples/heap/BoyerMoore/BoyerMoore.key index 5aa881eb14a..b54ea73969f 100644 --- a/key.ui/examples/heap/BoyerMoore/BoyerMoore.key +++ b/key.ui/examples/heap/BoyerMoore/BoyerMoore.key @@ -51,7 +51,7 @@ "options" : { "AUTO_INDUCTION_OPTIONS_KEY" : "AUTO_INDUCTION_OFF", "BLOCK_OPTIONS_KEY" : "BLOCK_CONTRACT_INTERNAL", - "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_OFF", + "CLASS_AXIOM_OPTIONS_KEY" : "CLASS_AXIOM_DELAYED", "DEP_OPTIONS_KEY" : "DEP_ON", "INF_FLOW_CHECK_PROPERTY" : "INF_FLOW_CHECK_FALSE", "LOOP_OPTIONS_KEY" : "LOOP_INVARIANT", diff --git a/key.ui/examples/heap/BoyerMoore/README.txt b/key.ui/examples/heap/BoyerMoore/README.txt index f56ae2a608a..9c748ddbecd 100644 --- a/key.ui/examples/heap/BoyerMoore/README.txt +++ b/key.ui/examples/heap/BoyerMoore/README.txt @@ -15,8 +15,9 @@ entries in the array hold m. Suggested by J.C. Filliâtre as an example during VerifyThis 24. -Currently the proofs do not go through automatically, the proof -files are checked in with the example. +Originally, the proofs did not go through automatically, with JML +proof scripts they now succeed. + @see https://en.wikipedia.org/wiki/Boyer-Moore_majority_vote_algorithm @author Mattias Ulbrich diff --git a/key.ui/examples/heap/BoyerMoore/src/BoyerMoore.java b/key.ui/examples/heap/BoyerMoore/src/BoyerMoore.java index dc8a350ff1a..abe99b428b1 100644 --- a/key.ui/examples/heap/BoyerMoore/src/BoyerMoore.java +++ b/key.ui/examples/heap/BoyerMoore/src/BoyerMoore.java @@ -59,11 +59,23 @@ public IntOpt bm(int[] a) { if(mc == 0) { mc = 1; mx = a[k]; + /*@ assert count(a, k+1, a[k]) <= count(a, k, a[k]) + 1 \by { + @ oss; + @ expand on: "self.count(a, k_0 + 1, a[k_0])"; + @ auto classAxioms:false; + @ }*/ } else if(mx == a[k]) { mc++; } else { mc--; } + /*@ assert (\forall int x; x != mx; 2 * count(a, k+1, x) <= k+1 - mc) \by { + @ oss; + @ obtain int x \from_goal; + @ expand on: "self.count(a, k_0 + 1, x)"; + @ instantiate var:"x" with: x; + @ auto classAxioms:false; + @ }*/ } if(mc == 0) return IntOpt.NONE; diff --git a/key.ui/examples/heap/quicksort/Quicksort.java b/key.ui/examples/heap/quicksort/Quicksort.java index 23d7bb6f0ff..431eaf04726 100644 --- a/key.ui/examples/heap/quicksort/Quicksort.java +++ b/key.ui/examples/heap/quicksort/Quicksort.java @@ -28,7 +28,10 @@ * The example has been added to show the power of proof * scripts. * - * @author Mattias Ulbrich, 2015 + * Translated to the use of JML proof scripts in 2025. Currently, + * this still increases the size of proof. + * + * @author Mattias Ulbrich, 2015, 2025 */ class Quicksort { @@ -58,9 +61,18 @@ public void sort(int[] array) { @*/ private void sort(int[] array, int from, int to) { if(from < to) { + //@ ghost \seq seq0 = \dl_array2seq(array); int splitPoint = split(array, from, to); + //@ ghost \seq seq1 = \dl_array2seq(array); sort(array, from, splitPoint-1); + //@ ghost \seq seq2 = \dl_array2seq(array); sort(array, splitPoint+1, to); + //@ ghost \seq seq3 = \dl_array2seq(array); + /*@ assert \dl_seqPerm(seq3, seq0) \by { + @ assert \dl_seqPerm(seq1, seq0) \by auto; + @ assert \dl_seqPerm(seq2, seq0) \by auto; + @ auto; + @ } */ } } @@ -97,6 +109,18 @@ private int split(int[] array, int from, int to) { int t = array[i]; array[i] = array[j]; array[j] = t; + /*@ assert \dl_seqPerm(\dl_array2seq(array), \old(\dl_array2seq(array))) \by { + @ oss; + @ rule "seqPermFromSwap"; + @ rule "andRight" \by { + @ case "Case 1": // the first of the two conjuncts is easy + @ auto; + @ case "Case 2": // the 2nd requires instantiations: + @ instantiate hide:true var:"iv" with:i; + @ instantiate hide:true var:"jv" with:j; + @ auto; + @ } + @ }; */ i++; } } @@ -104,6 +128,19 @@ private int split(int[] array, int from, int to) { array[to] = array[i]; array[i] = pivot; + /*@ assert \dl_seqPerm(\dl_array2seq(array), \old(\dl_array2seq(array))) \by { + @ oss; + @ rule "seqPermFromSwap"; + @ rule "andRight" \by { + @ case "Case 1": // the first of the two conjuncts is easy + @ auto; + @ case "Case 2": // the 2nd requires instantiations: + @ instantiate hide:true var:"iv" with:i; + @ instantiate hide:true var:"jv" with:to; + @ auto; + @ } + @ }; */ + return i; } diff --git a/key.ui/src/main/java/de/uka/ilkd/key/gui/IssueDialog.java b/key.ui/src/main/java/de/uka/ilkd/key/gui/IssueDialog.java index a882eb57afd..a90b89338b4 100644 --- a/key.ui/src/main/java/de/uka/ilkd/key/gui/IssueDialog.java +++ b/key.ui/src/main/java/de/uka/ilkd/key/gui/IssueDialog.java @@ -736,6 +736,9 @@ private void addHighlights(DefaultHighlighter dh, PositionedString ps) { } String source = txtSource.getText(); int offset = getOffsetFromLineColumn(source, pos); + while (offset < source.length() && Character.isWhitespace(source.charAt(offset))) { + offset++; + } int end = offset; while (end < source.length() && !Character.isWhitespace(source.charAt(end))) { end++; diff --git a/key.ui/src/main/java/de/uka/ilkd/key/gui/ProofScriptWorker.java b/key.ui/src/main/java/de/uka/ilkd/key/gui/ProofScriptWorker.java index ccb6b52727a..2f6df0d8b59 100644 --- a/key.ui/src/main/java/de/uka/ilkd/key/gui/ProofScriptWorker.java +++ b/key.ui/src/main/java/de/uka/ilkd/key/gui/ProofScriptWorker.java @@ -22,6 +22,7 @@ import de.uka.ilkd.key.scripts.ProofScriptEngine; import de.uka.ilkd.key.scripts.ScriptException; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -46,7 +47,8 @@ public class ProofScriptWorker extends SwingWorker<@Nullable Object, ProofScript /** * The proof script engine. */ - private final ProofScriptEngine engine; + private @MonotonicNonNull ProofScriptEngine engine; + private final JDialog monitor = new JDialog(MainWindow.getInstance(), "Running Script ...", ModalityType.MODELESS); private final JTextArea logArea = new JTextArea(); @@ -75,14 +77,15 @@ public ProofScriptWorker(KeYMediator mediator, KeyAst.ProofScript script, this.mediator = mediator; this.script = script; this.initiallySelectedGoal = initiallySelectedGoal; - engine = new ProofScriptEngine(script, initiallySelectedGoal); } @Override protected @Nullable Object doInBackground() throws Exception { try { + engine = new ProofScriptEngine(mediator.getSelectedProof()); + engine.setInitiallySelectedGoal(initiallySelectedGoal); engine.setCommandMonitor(observer); - engine.execute(mediator.getUI(), mediator.getSelectedProof()); + engine.execute(mediator.getUI(), script); } catch (InterruptedException ex) { LOGGER.debug("Proof macro has been interrupted:", ex); } @@ -171,7 +174,8 @@ public void done() { private void selectGoalOrNode() { final KeYSelectionModel selectionModel = mediator.getSelectionModel(); - if (!mediator.getSelectedProof().closed()) { + final Proof proof = mediator.getSelectedProof(); + if (proof != null && !proof.closed() && engine != null) { try { selectionModel .setSelectedGoal(engine.getStateMap().getFirstOpenAutomaticGoal()); diff --git a/key.ui/src/main/java/de/uka/ilkd/key/gui/StrategySelectionView.java b/key.ui/src/main/java/de/uka/ilkd/key/gui/StrategySelectionView.java index 37141d6090e..442e098cc4d 100644 --- a/key.ui/src/main/java/de/uka/ilkd/key/gui/StrategySelectionView.java +++ b/key.ui/src/main/java/de/uka/ilkd/key/gui/StrategySelectionView.java @@ -63,7 +63,7 @@ public final class StrategySelectionView extends JPanel implements TabPanel { /** * The always used {@link StrategyFactory}. */ - private static final StrategyFactory FACTORY = JavaProfile.DEFAULT; + private static final StrategyFactory FACTORY = JavaProfile.getDefault(); /** * The {@link StrategySettingsDefinition} of {@link #FACTORY} which defines the UI controls to diff --git a/key.ui/src/main/java/de/uka/ilkd/key/ui/ConsoleUserInterfaceControl.java b/key.ui/src/main/java/de/uka/ilkd/key/ui/ConsoleUserInterfaceControl.java index 4b33d65c79b..64554e31174 100644 --- a/key.ui/src/main/java/de/uka/ilkd/key/ui/ConsoleUserInterfaceControl.java +++ b/key.ui/src/main/java/de/uka/ilkd/key/ui/ConsoleUserInterfaceControl.java @@ -166,10 +166,10 @@ public void taskFinished(TaskFinishedInfo info) { var script = problemLoader.getProofScript(); if (script != null) { ProofScriptEngine pse = - new ProofScriptEngine(script); + new ProofScriptEngine(proof); this.taskStarted( new DefaultTaskStartedInfo(TaskKind.Macro, "Script started", 0)); - pse.execute(this, proof); + pse.execute(this, script); // The start and end messages are fake to persuade the system ... // All this here should refactored anyway ... this.taskFinished(new ProofMacroFinishedInfo(new SkipMacro(), proof)); diff --git a/key.util/src/main/java/org/key_project/util/collection/ImmutableList.java b/key.util/src/main/java/org/key_project/util/collection/ImmutableList.java index d83c99b9e90..1f10fb345e7 100644 --- a/key.util/src/main/java/org/key_project/util/collection/ImmutableList.java +++ b/key.util/src/main/java/org/key_project/util/collection/ImmutableList.java @@ -356,20 +356,25 @@ default T last() { remainder = remainder.tail(); } T result = remainder.head(); + // MU: I wonder why this is required. T may be a nullable type ... assert result != null : "@AssumeAssertion(nullness): this should never be null"; return result; } /** - * Get the n-th element of this list. + * Returns the element at the specified position in this list. * - * @param idx the 0-based index of the element - * @return the element at index idx. - * @throws IndexOutOfBoundsException if idx is less than 0 or at - * least {@link #size()}. - */ - default T get(int idx) { - return take(idx).head(); + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + default T get(int index) { + if (index < 0 || index >= size()) { + throw new IndexOutOfBoundsException(); + } else { + return take(index).head(); + } } } diff --git a/key.util/src/main/java/org/key_project/util/collection/ImmutableSet.java b/key.util/src/main/java/org/key_project/util/collection/ImmutableSet.java index 7eab97f1b7a..0168b88da93 100644 --- a/key.util/src/main/java/org/key_project/util/collection/ImmutableSet.java +++ b/key.util/src/main/java/org/key_project/util/collection/ImmutableSet.java @@ -43,6 +43,22 @@ public interface ImmutableSet return DefaultImmutableSet.nil(); } + static ImmutableSet of() { + return empty(); + } + + static ImmutableSet of(T... elems) { + return DefaultImmutableSet.fromImmutableList(ImmutableList.of(elems)); + } + + static ImmutableSet from(Iterable ts) { + ImmutableSet result = DefaultImmutableSet.nil(); + for (T t : ts) { + result = result.add(t); + } + return result; + } + /** * @return a {@code Set} containing the same elements as this {@code ImmutableSet} */ diff --git a/key.util/src/main/java/org/key_project/util/java/IOUtil.java b/key.util/src/main/java/org/key_project/util/java/IOUtil.java index 9a4666f7189..6ddcb062fa5 100644 --- a/key.util/src/main/java/org/key_project/util/java/IOUtil.java +++ b/key.util/src/main/java/org/key_project/util/java/IOUtil.java @@ -700,6 +700,40 @@ public static boolean copy(InputStream source, OutputStream target) throws IOExc } } + public static URL makeMemoryURL(String data) { + try { + return new URL("memory", "", 0, String.format("/%x", System.identityHashCode(data)), + new MemoryDataHandler(data)); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + private static final class MemoryDataHandler extends URLStreamHandler { + private final String data; + + public MemoryDataHandler(String data) { + this.data = data; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + // perhaps check the hash code too? + if (!u.getProtocol().equals("memory")) { + throw new IOException("Unsupported protocol"); + } + return new URLConnection(u) { + @Override + public void connect() {} + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(data.getBytes()); + } + }; + } + } + /** * Returns the current directory. * diff --git a/key.util/src/main/java/org/key_project/util/java/IntegerUtil.java b/key.util/src/main/java/org/key_project/util/java/IntegerUtil.java index 240bc59f135..bd6e6306659 100644 --- a/key.util/src/main/java/org/key_project/util/java/IntegerUtil.java +++ b/key.util/src/main/java/org/key_project/util/java/IntegerUtil.java @@ -4,6 +4,9 @@ package org.key_project.util.java; +import java.util.Collection; +import java.util.List; + public final class IntegerUtil { /** * Forbid instances. @@ -28,4 +31,27 @@ public static int factorial(int n) { return factorial; } } + + /** + * Creates a list of integers from {@code 0} (inclusive) to the size of the given collection + * (exclusive). + */ + public static List indexRangeOf(Collection coll) { + return rangeUntil(coll.size()); + } + + /** + * Creates a list of integers from {@code 0} (inclusive) to {@code size} (exclusive). + */ + private static List rangeUntil(int size) { + return range(0, size); + } + + /** + * Creates a list of integers from {@code from} (inclusive) to {@code untilExclusive} + * (exclusive). + */ + private static List range(int from, int untilExclusive) { + return java.util.stream.IntStream.range(from, untilExclusive).boxed().toList(); + } } diff --git a/key.util/src/main/java/org/key_project/util/java/StringUtil.java b/key.util/src/main/java/org/key_project/util/java/StringUtil.java index 3d1f6ed4212..31a1b57785f 100644 --- a/key.util/src/main/java/org/key_project/util/java/StringUtil.java +++ b/key.util/src/main/java/org/key_project/util/java/StringUtil.java @@ -554,4 +554,20 @@ public static String removeEmptyLines(String string) { return string.replaceAll("(?m)^[ \t]*\r?\n|\n$", ""); } + /** + * If the given text starts and ends with quotes (single or double), they will be stripped. + * + * @param text The text to check. + * @return The text without leading and trailing quotes or the original text if no quotes were + * present. + */ + public static String stripQuotes(String text) { + if (text.length() >= 2 && text.startsWith("\"") && text.endsWith("\"")) { + return text.substring(1, text.length() - 1); + } + if (text.length() >= 2 && text.startsWith("'") && text.endsWith("'")) { + return text.substring(1, text.length() - 1); + } + return text; + } }