From d102fbe178d92143faaccf43811ae54e6a849eb9 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 11:59:28 +0200 Subject: [PATCH 01/77] first steps to allow scripts in jml (cherry-picked from earlier attempt to implement JML proof scripts) # Conflicts: # key.core/src/main/java/de/uka/ilkd/key/java/Recoder2KeYConverter.java # key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JMLTransformer.java # key.core/src/main/java/de/uka/ilkd/key/java/recoderext/JmlAssert.java # key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java # key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java # key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java --- key.core/src/main/antlr4/JmlLexer.g4 | 2 ++ key.core/src/main/antlr4/JmlParser.g4 | 15 ++++++++-- key.core/src/main/antlr4/KeYLexer.g4 | 2 ++ .../ilkd/key/java/Recoder2KeYConverter.java | 2 +- .../key/java/recoderext/JMLTransformer.java | 2 +- .../ilkd/key/java/recoderext/JmlAssert.java | 16 ++++++++++- .../ilkd/key/java/statement/JmlAssert.java | 28 ++++++++++++++----- .../java/de/uka/ilkd/key/nparser/KeyAst.java | 14 ++++++++++ .../TextualJMLAssertStatement.java | 15 ++++++++-- .../uka/ilkd/key/speclang/njml/JmlFacade.java | 8 ++++++ .../key/speclang/njml/TextualTranslator.java | 6 ++-- 11 files changed, 93 insertions(+), 17 deletions(-) diff --git a/key.core/src/main/antlr4/JmlLexer.g4 b/key.core/src/main/antlr4/JmlLexer.g4 index 8b4eb5880cb..381dda8a5d2 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'; diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index 0060642e03b..e9a24ddd0a4 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -11,7 +11,6 @@ options { tokenVocab=JmlLexer; } public SyntaxErrorReporter getErrorReporter() { return errorReporter;} } - classlevel_comments: classlevel_comment* EOF; classlevel_comment: classlevel_element | modifiers | set_statement; classlevel_element0: modifiers? (classlevel_element modifiers?); @@ -202,11 +201,23 @@ 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 expression | UNREACHABLE) (SEMI_TOPLEVEL | assertionProof); //breaks_clause: BREAKS expression; //continues_clause: CONTINUES expression; //returns_clause: RETURNS expression; +// --- proof scripts in JML +assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; +proofCmd: + cmd=IDENT ( (argLabel=IDENT COLON)? proofArg )* + ( SEMI | BY proofCmd | LBRACE (( proofCmd )+ | proofCmdCase) RBRACE ) + ; +proofCmdCase: + CASE ( STRING_LITERAL )? COLON ( proofCmd )* + | DEFAULT COLON ( proofCmd )* + ; +proofArg: expression; +// --- mergeparamsspec: MERGE_PARAMS diff --git a/key.core/src/main/antlr4/KeYLexer.g4 b/key.core/src/main/antlr4/KeYLexer.g4 index 544c9371a42..0a692adf53c 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'; 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..ba9ae8c50be 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,7 @@ 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.getCondition(), ja.getAssertionProof(), positionInfo(ja)); } // ------------------- declaration --------------------- 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..2b481de72ec 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,7 @@ 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()); 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..2e84cfd5882 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 @@ -5,7 +5,10 @@ import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; - +import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement.Kind; +import de.uka.ilkd.key.speclang.njml.JmlParser.AssertionProofContext; +import de.uka.ilkd.key.speclang.njml.LabeledParserRuleContext; +import org.jspecify.annotations.Nullable; import recoder.java.ProgramElement; import recoder.java.SourceVisitor; import recoder.java.Statement; @@ -22,6 +25,7 @@ public class JmlAssert extends JavaStatement { * The kind of this statement either ASSERT or ASSUME */ private final TextualJMLAssertStatement.Kind kind; + private final KeyAst.@Nullable JMLProofScript assertionProof; /** @@ -35,8 +39,13 @@ public class JmlAssert extends JavaStatement { * @param condition the condition for this statement */ public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition) { + this(kind, condition, null); + } + + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, KeyAst.@Nullable JMLProofScript assertionProof) { this.kind = kind; this.condition = condition; + this.assertionProof = assertionProof; } /** @@ -48,6 +57,7 @@ public JmlAssert(JmlAssert proto) { super(proto); this.kind = proto.kind; this.condition = proto.condition; + this.assertionProof = proto.assertionProof; } public TextualJMLAssertStatement.Kind getKind() { @@ -58,6 +68,10 @@ public KeyAst.Expression getCondition() { return condition; } + public KeyAst.@Nullable JMLProofScript getAssertionProof() { + return assertionProof; + } + @Override public int getChildCount() { return 0; 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..5d9291d790a 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 @@ -3,16 +3,17 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.java.statement; -import java.util.Objects; - 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.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; - +import org.jspecify.annotations.Nullable; import org.key_project.util.ExtList; +import java.util.Objects; + +import de.uka.ilkd.key.nparser.KeyAst; + /** * A JML assert statement. * @@ -32,18 +33,25 @@ public class JmlAssert extends JavaStatement { /** * 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, KeyAst.Expression condition, KeyAst.@Nullable JMLProofScript assertionProof, PositionInfo positionInfo) { super(positionInfo); this.kind = kind; this.condition = condition; + this.assertionProof = assertionProof; } /** @@ -53,10 +61,12 @@ public JmlAssert(ExtList children) { super(children); this.kind = Objects.requireNonNull(children.get(TextualJMLAssertStatement.Kind.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.condition, other.assertionProof, other.getPositionInfo()); } public TextualJMLAssertStatement.Kind getKind() { @@ -148,6 +158,10 @@ protected int computeHashCode() { return System.identityHashCode(this); } + public KeyAst.@Nullable JMLProofScript getAssertionProof() { + return assertionProof; + } + @Override public int getChildCount() { return 0; 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..358b5e0cffc 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 @@ -223,6 +223,20 @@ public Expression(JmlParser.@NonNull ExpressionContext ctx) { } } + public static class JMLProofScript extends KeyAst { + 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 static class Term extends KeyAst { Term(KeYParser.TermContext ctx) { 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..457c92792b1 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 @@ -4,22 +4,27 @@ package de.uka.ilkd.key.speclang.jml.pretranslation; import de.uka.ilkd.key.nparser.KeyAst; - -import org.key_project.util.collection.ImmutableSLList; - import org.antlr.v4.runtime.RuleContext; +import org.jspecify.annotations.Nullable; +import org.key_project.util.collection.ImmutableSLList; /** * A JML assert/assume statement. */ public class TextualJMLAssertStatement extends TextualJMLConstruct { private final KeyAst.Expression context; + private final KeyAst.@Nullable JMLProofScript assertionProof; private final Kind kind; public TextualJMLAssertStatement(Kind kind, KeyAst.Expression clause) { + this(kind, clause, null); + } + + public TextualJMLAssertStatement(Kind kind, KeyAst.Expression clause, KeyAst.@Nullable JMLProofScript assertionProof) { super(ImmutableSLList.nil(), kind.toString() + " " + clause); this.kind = kind; this.context = clause; + this.assertionProof = assertionProof; } public KeyAst.Expression getContext() { @@ -77,4 +82,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/njml/JmlFacade.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlFacade.java index 80b93be0ce9..0099c13e0bf 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,6 +3,7 @@ * 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; @@ -114,4 +115,11 @@ private static ParserRuleContext getExpressionContext(JmlLexer lexer) { p.getErrorReporter().throwException(); return ctx; } + + public static void main(String[] args) throws IOException { + String input = new String(System.in.readAllBytes()); + var parser = createParser(createLexer(input)); + 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/TextualTranslator.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/TextualTranslator.java index fa08e4cb385..28ec1c970f7 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,10 @@ 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())); constructs = constructs.append(b); return null; } From 740b04613fa4d8f9bfd269f28ddcaf1b9f866e20 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 13:26:57 +0200 Subject: [PATCH 02/77] first working example of a proof script in Java code (albeit with JavaDL arguments) (cherry-picked from earlier attempt to implement JML proof scripts) --- key.core/src/main/antlr4/JmlParser.g4 | 7 +- .../key/java/visitor/CreatingASTVisitor.java | 9 ++ .../de/uka/ilkd/key/pp/PrettyPrinter.java | 6 + .../uka/ilkd/key/scripts/BranchesCommand.java | 122 ++++++++++++++++++ .../de/uka/ilkd/key/scripts/EngineState.java | 11 ++ .../ilkd/key/scripts/ProofScriptEngine.java | 1 + .../ilkd/key/scripts/meta/ValueInjector.java | 2 +- .../TextualJMLAssertStatement.java | 3 +- .../de.uka.ilkd.key.macros.ProofMacro | 1 + ...de.uka.ilkd.key.scripts.ProofScriptCommand | 1 + 10 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index e9a24ddd0a4..a2ecd8287ae 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -206,17 +206,18 @@ assert_statement: (ASSERT expression | UNREACHABLE) (SEMI_TOPLEVEL | assertionPr //continues_clause: CONTINUES expression; //returns_clause: RETURNS expression; + // --- proof scripts in JML -assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; +assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; proofCmd: - cmd=IDENT ( (argLabel=IDENT COLON)? proofArg )* + cmd=IDENT ( proofArg )* ( SEMI | BY proofCmd | LBRACE (( proofCmd )+ | proofCmdCase) RBRACE ) ; proofCmdCase: CASE ( STRING_LITERAL )? COLON ( proofCmd )* | DEFAULT COLON ( proofCmd )* ; -proofArg: expression; +proofArg: (argLabel=IDENT COLON)? expression; // --- mergeparamsspec: 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..b5d2402d84a 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,12 +1513,21 @@ public void performActionOnJmlAssert(JmlAssert x) { ProgramElement createNewElement(ExtList changeList) { changeList.add(x.getKind()); changeList.add(x.getCondition()); + changeList.add(x.getAssertionProof()); return new JmlAssert(changeList); } }; def.doAction(x); } + // XXX Is this still needed (merge artifact) + @Override + public void performActionOnJmlAssertCondition(final Term cond) { + // should only be called by walk(), which puts an ExtList on the stack + assert stack.peek() != null; + stack.peek().add(cond); + } + /** * returns the position of pe2 in the virtual child array of pe1 * 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/scripts/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java new file mode 100644 index 00000000000..f8b283237a9 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java @@ -0,0 +1,122 @@ +package de.uka.ilkd.key.macros.scripts; + +import de.uka.ilkd.key.logic.Semisequent; +import de.uka.ilkd.key.logic.Sequent; +import de.uka.ilkd.key.logic.SequentFormula; +import de.uka.ilkd.key.logic.Term; +import de.uka.ilkd.key.macros.scripts.meta.Option; +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.util.collection.ImmutableList; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Stack; +import java.util.function.Function; + +public class BranchesCommand extends AbstractCommand { + public BranchesCommand() { + super(Parameters.class); + } + + @Override + public Parameters evaluateArguments(EngineState state, Map arguments) + throws Exception { + return state.getValueInjector().inject(this, new Parameters(), arguments); + } + + @Override + public void execute(Parameters args) throws ScriptException, InterruptedException { + Stack stack = (Stack) state.getUserData("_branchStack"); + if (stack == null) { + stack = new Stack<>(); + state.putUserData("_branchStack", stack); + } + + switch (args.mode) { + case "push": + Node node = state.getFirstOpenAutomaticGoal().node(); + // this is the first goal. The parent is the decision point + node = node.parent(); + 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; + default: + throw new ScriptException(); + } + } + + private Goal findGoalByName(Node root, String branch) throws ScriptException { + Iterator it = root.childrenIterator(); + List knownBranchLabels = new ArrayList<>(); + while (it.hasNext()) { + Node node = it.next(); + String label = node.getNodeInfo().getBranchLabel(); + knownBranchLabels.add(label); + if (branch.equals(label)) { + return findGoalByNode(root.proof(), node); + } + } + 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(); + } + + @Override + public String getName() { + return "branches"; + } + + public static class Parameters { + /** A formula defining the goal to select */ + @Option(value = "#2", required = true) + public String mode; + @Option(value = "branch", required = false) + public String branch; + @Option(value = "child", required = false) + public int child; + } + +} 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..bd9899f13a6 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; @@ -61,6 +62,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. @@ -362,4 +365,12 @@ public NamespaceSet getCurrentNamespaces() { public ExprEvaluator getEvaluator() { return exprEvaluator; } + + public void putUserData(String key, Object val) { + userData.put(key, val); + } + + public Object getUserData(String key) { + return userData.get(key); + } } 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..59a0eb98bf2 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 @@ -154,6 +154,7 @@ public void execute(AbstractUserInterfaceControl uiControl, List Date: Mon, 30 Jan 2023 18:58:25 +0100 Subject: [PATCH 03/77] added missing files for script aware macros (cherry-picked from earlier attempt to implement JML proof scripts) --- .../ilkd/key/macros/ApplyScriptsMacro.java | 147 ++++++++++++++++++ .../ilkd/key/macros/ScriptAwareAutoMacro.java | 109 +++++++++++++ .../uka/ilkd/key/macros/ScriptAwareMacro.java | 61 ++++++++ 3 files changed, 317 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareAutoMacro.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java 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..2adc33c57c3 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java @@ -0,0 +1,147 @@ +package de.uka.ilkd.key.macros; + +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.SourceElement; +import de.uka.ilkd.key.java.statement.JmlAssert; +import de.uka.ilkd.key.logic.PosInOccurrence; +import de.uka.ilkd.key.logic.Term; +import de.uka.ilkd.key.logic.op.UpdateApplication; +import de.uka.ilkd.key.macros.scripts.ProofScriptEngine; +import de.uka.ilkd.key.parser.Location; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.prover.ProverTaskListener; +import de.uka.ilkd.key.rule.JmlAssertBuiltInRuleApp; +import de.uka.ilkd.key.rule.JmlAssertRule; +import de.uka.ilkd.key.rule.RuleApp; +import de.uka.ilkd.key.speclang.njml.JmlParser.AssertionProofContext; +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 org.key_project.util.collection.ImmutableList; + +import java.net.URL; +import java.util.Map; + +public class ApplyScriptsMacro extends AbstractProofMacro { + + private final ProofMacro fallBackMacro; + + public ApplyScriptsMacro(ProofMacro fallBackMacro) { + this.fallBackMacro = fallBackMacro; + } + + @Override + public String getName() { + return "null"; + } + + @Override + public String getCategory() { + return "null"; + } + + @Override + public String getDescription() { + return "null"; + } + + @Override + public boolean canApplyTo(Proof proof, ImmutableList goals, PosInOccurrence posInOcc) { + return fallBackMacro.canApplyTo(proof, goals, posInOcc) + || goals.exists(g -> getScript(g) != null); + } + + private static AssertionProofContext getScript(Goal goal) { + RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); + if (ruleApp instanceof JmlAssertBuiltInRuleApp) { + Term target = ruleApp.posInOccurrence().subTerm(); + if (target.op() instanceof UpdateApplication) { + target = UpdateApplication.getTarget(target); + } + final SourceElement activeStatement = JavaTools.getActiveStatement(target.javaBlock()); + if (activeStatement instanceof JmlAssert) { + return ((JmlAssert) activeStatement).getAssertionProof(); + } + } + return null; + } + + @Override + public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, + ImmutableList goals, PosInOccurrence posInOcc, ProverTaskListener listener) + throws InterruptedException, Exception { + for (Goal goal : goals) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + AssertionProofContext proofCtx = getScript(goal); + if (proofCtx == null) { + fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + continue; + } + String renderedProof = renderProof(proofCtx); + // TODO get this location from the jmlAssertion statement ... + Location loc = new Location(new URL("file:///tmp/unknown.key"), 42, 42); + ProofScriptEngine pse = new ProofScriptEngine(renderedProof, loc, goal); + System.out.println("---- Script"); + System.out.println(renderedProof); + pse.execute((AbstractUserInterfaceControl) uic, proof); + } + return new ProofMacroFinishedInfo(this, proof); + } + + private static String renderProof(AssertionProofContext ctx) { + StringBuilder sb = new StringBuilder(); + for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { + renderProofCmd(proofCmdContext, sb); + } + return sb.toString(); + } + + private static void renderProofCmd(ProofCmdContext ctx, StringBuilder sb) { + if (ctx.cmd != null) { + sb.append(ctx.cmd.getText()).append(" "); + for (ProofArgContext arg : ctx.proofArg()) { + if (arg.argLabel != null) { + sb.append(arg.argLabel.getText()).append("="); + } + sb.append(arg.token.getText()).append(" "); + } + sb.append(";\n"); + + } else if (ctx.assertion != null) { + sb.append("cut ").append(ctx.assertion).append(";\n"); + sb.append("branches \"push\";\n"); + sb.append("branches \"select\" child=0;\n"); + if (ctx.proofCmd().isEmpty()) { + sb.append("auto;\n"); + } else { + for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { + renderProofCmd(proofCmdContext, sb); + } + } + sb.append("branches \"select\" child=1;\n"); + sb.append("branches \"pop\";\n"); + + } else if (!ctx.proofCmdCase().isEmpty()) { + sb.append("branches \"push\";\n"); + int no = 0; + for (ProofCmdCaseContext caseContext : ctx.proofCmdCase()) { + if (caseContext.STRING_LITERAL() != null) { + sb.append("branches \"select\" branch=") + .append(caseContext.STRING_LITERAL().getText()).append(";\n"); + } else { + sb.append("branches \"select\" child=").append(no++).append(";\n"); + } + for (ProofCmdContext proofCmdContext : caseContext.proofCmd()) { + renderProofCmd(proofCmdContext, sb); + } + } + sb.append("branches \"pop\";\n"); + } + + } +} 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..532d2ca7a2e --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareAutoMacro.java @@ -0,0 +1,109 @@ +//// 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..2dfebc32b83 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java @@ -0,0 +1,61 @@ +// 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. + * + * It is experimental. + * + * It performs the following steps: + *
    + *
  1. Finish symbolic execution + *
  2. >Separate proof obligations" + + *
  3. Expand invariant definitions + *
  4. Try to close all proof obligations + *
+ * + * @author mattias ulbrich + */ +public class ScriptAwareMacro extends SequentialProofMacro { + + private final ProofMacro autoMacro = new 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 }; + } +} From 210c0ca34efe777253de0fffa9d16776f78e5e3a Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 15:50:45 +0200 Subject: [PATCH 04/77] more infrastructure for proof scripts # Conflicts: # key.core/src/main/java/de/uka/ilkd/key/logic/Semisequent.java # key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java # key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java # key.core/src/main/java/de/uka/ilkd/key/strategy/StrategyProperties.java --- key.core/src/main/antlr4/JmlParser.g4 | 4 +- .../ilkd/key/java/statement/JmlAssert.java | 23 ++++ .../key/java/visitor/CreatingASTVisitor.java | 8 -- .../ilkd/key/macros/ApplyScriptsMacro.java | 123 +++++++++--------- .../java/de/uka/ilkd/key/nparser/KeyAst.java | 42 +++++- .../de/uka/ilkd/key/rule/JmlAssertRule.java | 3 +- .../uka/ilkd/key/scripts/BranchesCommand.java | 27 ++-- .../uka/ilkd/key/scripts/meta/Converter.java | 11 +- .../ilkd/key/scripts/meta/ValueInjector.java | 9 +- .../jml/translation/JMLSpecFactory.java | 4 +- 10 files changed, 152 insertions(+), 102 deletions(-) diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index a2ecd8287ae..fe632eef7d5 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -211,10 +211,10 @@ assert_statement: (ASSERT expression | UNREACHABLE) (SEMI_TOPLEVEL | assertionPr assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; proofCmd: cmd=IDENT ( proofArg )* - ( SEMI | BY proofCmd | LBRACE (( proofCmd )+ | proofCmdCase) RBRACE ) + ( SEMI | BY proofCmd | LBRACE (proofCmd+ | proofCmdCase+) RBRACE ) ; proofCmdCase: - CASE ( STRING_LITERAL )? COLON ( proofCmd )* + CASE ( label=STRING_LITERAL )? COLON ( proofCmd )* | DEFAULT COLON ( proofCmd )* ; proofArg: (argLabel=IDENT COLON)? expression; 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 5d9291d790a..bc139142a83 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 @@ -6,13 +6,21 @@ 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.JTerm; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; +import de.uka.ilkd.key.speclang.njml.LabeledParserRuleContext; +import org.antlr.v4.runtime.ParserRuleContext; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.key_project.util.ExtList; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import de.uka.ilkd.key.nparser.KeyAst; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.Immutables; /** * A JML assert statement. @@ -176,4 +184,19 @@ 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; + } } 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 b5d2402d84a..fff08c99448 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 @@ -1520,14 +1520,6 @@ ProgramElement createNewElement(ExtList changeList) { def.doAction(x); } - // XXX Is this still needed (merge artifact) - @Override - public void performActionOnJmlAssertCondition(final Term cond) { - // should only be called by walk(), which puts an ExtList on the stack - assert stack.peek() != null; - stack.peek().add(cond); - } - /** * returns the position of pe2 in the virtual child array of pe1 * 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 index 2adc33c57c3..f032e190773 100644 --- 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 @@ -3,26 +3,31 @@ 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.Position; import de.uka.ilkd.key.java.SourceElement; import de.uka.ilkd.key.java.statement.JmlAssert; -import de.uka.ilkd.key.logic.PosInOccurrence; -import de.uka.ilkd.key.logic.Term; +import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.op.UpdateApplication; -import de.uka.ilkd.key.macros.scripts.ProofScriptEngine; +import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; -import de.uka.ilkd.key.prover.ProverTaskListener; import de.uka.ilkd.key.rule.JmlAssertBuiltInRuleApp; -import de.uka.ilkd.key.rule.JmlAssertRule; -import de.uka.ilkd.key.rule.RuleApp; -import de.uka.ilkd.key.speclang.njml.JmlParser.AssertionProofContext; +import de.uka.ilkd.key.scripts.ProofScriptEngine; +import de.uka.ilkd.key.scripts.ScriptCommandAst; 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 org.jspecify.annotations.NonNull; +import org.key_project.prover.engine.ProverTaskListener; +import org.key_project.prover.rules.RuleApp; +import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.util.collection.ImmutableList; -import java.net.URL; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; public class ApplyScriptsMacro extends AbstractProofMacro { @@ -49,21 +54,21 @@ public String getDescription() { } @Override - public boolean canApplyTo(Proof proof, ImmutableList goals, PosInOccurrence posInOcc) { + public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, PosInOccurrence posInOcc) { return fallBackMacro.canApplyTo(proof, goals, posInOcc) || goals.exists(g -> getScript(g) != null); } - private static AssertionProofContext getScript(Goal goal) { + private static KeyAst.JMLProofScript getScript(Goal goal) { RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); if (ruleApp instanceof JmlAssertBuiltInRuleApp) { - Term target = ruleApp.posInOccurrence().subTerm(); + 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) { - return ((JmlAssert) activeStatement).getAssertionProof(); + if (activeStatement instanceof JmlAssert jmlAssert) { + return jmlAssert.getAssertionProof(); } } return null; @@ -77,15 +82,15 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, if (Thread.interrupted()) { throw new InterruptedException(); } - AssertionProofContext proofCtx = getScript(goal); - if (proofCtx == null) { + KeyAst.JMLProofScript proofScript = getScript(goal); + if (proofScript == null) { fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); continue; } - String renderedProof = renderProof(proofCtx); + List renderedProof = renderProof(proofScript); // TODO get this location from the jmlAssertion statement ... - Location loc = new Location(new URL("file:///tmp/unknown.key"), 42, 42); - ProofScriptEngine pse = new ProofScriptEngine(renderedProof, loc, goal); + Location loc = new Location(new URI("unknown:XXX"), Position.UNDEFINED); + ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); System.out.println("---- Script"); System.out.println(renderedProof); pse.execute((AbstractUserInterfaceControl) uic, proof); @@ -93,55 +98,53 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, return new ProofMacroFinishedInfo(this, proof); } - private static String renderProof(AssertionProofContext ctx) { - StringBuilder sb = new StringBuilder(); - for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { - renderProofCmd(proofCmdContext, sb); + private static List renderProof(KeyAst.JMLProofScript script) { + List result = new ArrayList<>(); + for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext)); } - return sb.toString(); + return result; } - private static void renderProofCmd(ProofCmdContext ctx, StringBuilder sb) { - if (ctx.cmd != null) { - sb.append(ctx.cmd.getText()).append(" "); - for (ProofArgContext arg : ctx.proofArg()) { - if (arg.argLabel != null) { - sb.append(arg.argLabel.getText()).append("="); - } - sb.append(arg.token.getText()).append(" "); - } - sb.append(";\n"); - - } else if (ctx.assertion != null) { - sb.append("cut ").append(ctx.assertion).append(";\n"); - sb.append("branches \"push\";\n"); - sb.append("branches \"select\" child=0;\n"); - if (ctx.proofCmd().isEmpty()) { - sb.append("auto;\n"); + private static List renderProofCmd(ProofCmdContext ctx) { + List result = new ArrayList<>(); + + // Push the current branch context + result.add(new ScriptCommandAst("branches", Map.of(), List.of("push"))); + + // Compose the command itself + Map named = new HashMap<>(); + List positional = new ArrayList<>(); + for (ProofArgContext argContext : ctx.proofArg()) { + // FIXME: actually render the argument value. This is just a placeholder + var value = argContext.expression().getText(); + if (argContext.argLabel != null) { + named.put(argContext.argLabel.getText(), value); } else { - for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { - renderProofCmd(proofCmdContext, sb); - } + positional.add(value); } - sb.append("branches \"select\" child=1;\n"); - sb.append("branches \"pop\";\n"); - - } else if (!ctx.proofCmdCase().isEmpty()) { - sb.append("branches \"push\";\n"); - int no = 0; - for (ProofCmdCaseContext caseContext : ctx.proofCmdCase()) { - if (caseContext.STRING_LITERAL() != null) { - sb.append("branches \"select\" branch=") - .append(caseContext.STRING_LITERAL().getText()).append(";\n"); - } else { - sb.append("branches \"select\" child=").append(no++).append(";\n"); - } - for (ProofCmdContext proofCmdContext : caseContext.proofCmd()) { - renderProofCmd(proofCmdContext, sb); - } + } + result.add(new ScriptCommandAst(ctx.cmd.getText(), named, positional, null, Location.fromToken(ctx.start))); + + // handle proofCmd if present + if(!ctx.proofCmd().isEmpty()) { + result.add(new ScriptCommandAst("branches", Map.of("child", 0), List.of("select"))); + for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext)); } - sb.append("branches \"pop\";\n"); } + // handle proofCmdCase if present + for (ProofCmdCaseContext pcase : ctx.proofCmdCase()) { + result.add(new ScriptCommandAst("branches", Map.of("name", pcase.label), List.of("select"))); + for (ProofCmdContext proofCmdContext : pcase.proofCmd()) { + result.addAll(renderProofCmd(proofCmdContext)); + } + } + + // Pop the branch stack + result.add(new ScriptCommandAst("branches", Map.of(), List.of("pop"))); + + return result; } } 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 358b5e0cffc..85e2e7918c5 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 @@ -23,6 +23,7 @@ import de.uka.ilkd.key.settings.ProofSettings; import de.uka.ilkd.key.speclang.njml.JmlParser; +import org.key_project.util.collection.ImmutableList; import org.key_project.util.java.StringUtil; import org.antlr.v4.runtime.CharStream; @@ -47,7 +48,7 @@ */ public abstract class KeyAst { - final @NonNull T ctx; + public final @NonNull T ctx; protected KeyAst(@NonNull T ctx) { this.ctx = ctx; @@ -235,6 +236,45 @@ public static JMLProofScript fromContext(JmlParser.AssertionProofContext ctx) { return new JMLProofScript(ctx); } } + + /** + * Collect all JML expressions in a script (and potentially sub-blocks) + * @param cmd the command to collect from + * @return a list in reverse(!) order of all expressions in cmd + */ + private static ImmutableList collectTerms(JmlParser.ProofCmdContext cmd) { + ImmutableList result = ImmutableList.of(); + for (JmlParser.ProofArgContext arg : cmd.proofArg()) { + JmlParser.ExpressionContext exp = arg.expression(); + if (exp != null && exp.start.getType() != JmlParser.STRING_LITERAL) { + result = result.prepend(exp); + } + } + for (JmlParser.ProofCmdContext childCmd : cmd.proofCmd()) { + result = result.prepend(collectTerms(childCmd)); + } + if (cmd.proofCmdCase() != null) { + for (JmlParser.ProofCmdCaseContext pcase : cmd.proofCmdCase()) { + for (JmlParser.ProofCmdContext childCmd : pcase.proofCmd()) { + result = result.prepend(collectTerms(childCmd)); + } + } + } + return result; + } + + /** + * 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() { + ImmutableList result = ImmutableList.of(); + for (JmlParser.ProofCmdContext cmd : ctx.proofCmd()) { + result.prepend(collectTerms(cmd)); + } + return result.reverse(); + } } 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..47dcc48c86b 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,7 @@ 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.mgt.SpecificationRepository; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement.Kind; import de.uka.ilkd.key.util.MiscTools; @@ -132,7 +133,7 @@ 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( 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 index f8b283237a9..4e47d1bb3ab 100644 --- 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 @@ -1,38 +1,27 @@ -package de.uka.ilkd.key.macros.scripts; +package de.uka.ilkd.key.scripts; -import de.uka.ilkd.key.logic.Semisequent; -import de.uka.ilkd.key.logic.Sequent; -import de.uka.ilkd.key.logic.SequentFormula; -import de.uka.ilkd.key.logic.Term; -import de.uka.ilkd.key.macros.scripts.meta.Option; +import de.uka.ilkd.key.scripts.meta.Option; 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.util.collection.ImmutableList; import java.util.ArrayList; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Stack; -import java.util.function.Function; -public class BranchesCommand extends AbstractCommand { +public class BranchesCommand extends AbstractCommand { public BranchesCommand() { super(Parameters.class); } @Override - public Parameters evaluateArguments(EngineState state, Map arguments) - throws Exception { - return state.getValueInjector().inject(this, new Parameters(), arguments); - } + public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new BranchesCommand.Parameters(), arguments); - @Override - public void execute(Parameters args) throws ScriptException, InterruptedException { Stack stack = (Stack) state.getUserData("_branchStack"); if (stack == null) { stack = new Stack<>(); @@ -111,11 +100,11 @@ public String getName() { public static class Parameters { /** A formula defining the goal to select */ - @Option(value = "#2", required = true) + @Option(value = "#2") public String mode; - @Option(value = "branch", required = false) + @Option(value = "branch") public String branch; - @Option(value = "child", required = false) + @Option(value = "child") public int child; } 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/ValueInjector.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java index 66c80aa8603..94e95310a9c 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 @@ -301,10 +301,11 @@ 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) { 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..50312133e9b 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 @@ -1513,10 +1513,10 @@ public void translateJmlAssertCondition(final JmlAssert jmlAssert, final IProgra .exceptionVariable(pv.excVar) .atPres(pv.atPres) .atBefore(pv.atBefores); - JTerm expr = io.translateTerm(jmlAssert.getCondition()); + 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) { From 751d11720086187137e2b7443a1c5a81400db16e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 4 Feb 2023 16:06:14 +0100 Subject: [PATCH 05/77] more infrastructure for proof scripts (cherry-picked from earlier attempt to implement JML proof scripts) --- .../ilkd/key/macros/ApplyScriptsMacro.java | 3 ++ .../de/uka/ilkd/key/scripts/RuleCommand.java | 13 ++++--- .../de/uka/ilkd/key/scripts/SetCommand.java | 34 +++++++++++++++++-- .../ilkd/key/strategy/StrategyProperties.java | 3 +- .../prover/sequent/Semisequent.java | 6 ++++ 5 files changed, 48 insertions(+), 11 deletions(-) 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 index f032e190773..5e349551286 100644 --- 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 @@ -10,6 +10,9 @@ import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; +import de.uka.ilkd.key.pp.LogicPrinter; +import de.uka.ilkd.key.pp.NotationInfo; +import de.uka.ilkd.key.pp.ProgramPrinter; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.rule.JmlAssertBuiltInRuleApp; 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..70592408954 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 @@ -249,7 +249,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."); } @@ -290,7 +290,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 +299,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 +321,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 +330,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 +350,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 = 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..e32b7602b1f 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,6 +6,8 @@ import java.util.HashMap; import java.util.Map; +import java.util.Properties; +import java.util.Stack; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; @@ -34,7 +36,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup final Proof proof = state.getProof(); - final StrategyProperties newProps = + StrategyProperties newProps = proof.getSettings().getStrategySettings().getActiveStrategyProperties(); if (args.oneStepSimplification != null) { @@ -43,6 +45,31 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup : StrategyProperties.OSS_OFF); Strategy.updateStrategySettings(proof, newProps); OneStepSimplifier.refreshOSS(proof); + } else if (args.proofSteps != null) { + state.setMaxAutomaticSteps(args.proofSteps); + } else if (args.key != null) { + newProps.setProperty(args.key, args.value); + updateStrategySettings(newProps); + } else 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 + newProps = stack.pop(); + updateStrategySettings(newProps); + break; + default: + throw new IllegalArgumentException("stack must be either push or pop."); + } + } else { + throw new IllegalArgumentException("You have to set oss, steps, stack, or key and value."); } if (args.proofSteps != null) { state.setMaxAutomaticSteps(args.proofSteps); @@ -116,8 +143,11 @@ public static class Parameters { @Option(value = "steps") public @Nullable Integer proofSteps; - /***/ + /** key-value pairs to set */ @OptionalVarargs public Map settings = HashMap.newHashMap(0); + + @Option(value = "stack", required = false) + public String stackAction; } } 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.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] From 0a401affe1ce4049c47b884c77381ece00aab31c Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 20:54:32 +0200 Subject: [PATCH 06/77] more infrastructure for proof scripts (cherry-picked from earlier attempt to implement JML proof scripts) # Conflicts: # key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java --- .../ilkd/key/macros/ApplyScriptsMacro.java | 71 +++++++++++++------ .../java/de/uka/ilkd/key/nparser/KeyAst.java | 4 +- .../uka/ilkd/key/scripts/BranchesCommand.java | 6 +- .../de/uka/ilkd/key/scripts/SetCommand.java | 28 ++++---- .../uka/ilkd/key/scripts/meta/Argument.java | 2 +- .../org/key_project/util/java/StringUtil.java | 16 +++++ 6 files changed, 85 insertions(+), 42 deletions(-) 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 index 5e349551286..e5edea309a0 100644 --- 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 @@ -3,35 +3,34 @@ 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.Position; +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.logic.JTerm; import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; -import de.uka.ilkd.key.pp.LogicPrinter; -import de.uka.ilkd.key.pp.NotationInfo; -import de.uka.ilkd.key.pp.ProgramPrinter; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; +import de.uka.ilkd.key.proof.mgt.SpecificationRepository; 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.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 org.antlr.v4.runtime.ParserRuleContext; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.key_project.logic.op.Function; import org.key_project.prover.engine.ProverTaskListener; 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 java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class ApplyScriptsMacro extends AbstractProofMacro { @@ -62,7 +61,7 @@ public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, PosIn || goals.exists(g -> getScript(g) != null); } - private static KeyAst.JMLProofScript getScript(Goal goal) { + private static JmlAssert getScript(Goal goal) { RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); if (ruleApp instanceof JmlAssertBuiltInRuleApp) { JTerm target = (JTerm) ruleApp.posInOccurrence().subTerm(); @@ -71,7 +70,7 @@ private static KeyAst.JMLProofScript getScript(Goal goal) { } final SourceElement activeStatement = JavaTools.getActiveStatement(target.javaBlock()); if (activeStatement instanceof JmlAssert jmlAssert) { - return jmlAssert.getAssertionProof(); + return jmlAssert; } } return null; @@ -85,14 +84,15 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, if (Thread.interrupted()) { throw new InterruptedException(); } - KeyAst.JMLProofScript proofScript = getScript(goal); - if (proofScript == null) { + JmlAssert jmlAssert = getScript(goal); + if (jmlAssert == null || jmlAssert.getAssertionProof() == null) { + // no script found, use fallback macro fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); continue; } - List renderedProof = renderProof(proofScript); - // TODO get this location from the jmlAssertion statement ... - Location loc = new Location(new URI("unknown:XXX"), Position.UNDEFINED); + KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); + Map termMap = getTermMap(jmlAssert, proof.getServices()); + List renderedProof = renderProof(proofScript, termMap, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); System.out.println("---- Script"); System.out.println(renderedProof); @@ -101,15 +101,31 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, return new ProofMacroFinishedInfo(this, proof); } - private static List renderProof(KeyAst.JMLProofScript script) { + private Map getTermMap(JmlAssert jmlAssert, 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 = jmlspec.terms().tail(); + ImmutableList jmlExprs = jmlAssert.collectTerms().tail(); + Map result = new IdentityHashMap<>(); + assert terms.size() == jmlExprs.size(); + for(int i = 0; i < terms.size(); i++) { + // TODO build a map from jmlExprs.get(i) to terms.get(i) + result.put(jmlExprs.get(i), terms.get(i)); + } + return result; + } + + private static List renderProof(KeyAst.JMLProofScript script, Map termMap, Services services) { List result = new ArrayList<>(); for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext)); + result.addAll(renderProofCmd(proofCmdContext, termMap, services)); } return result; } - private static List renderProofCmd(ProofCmdContext ctx) { + private static List renderProofCmd(ProofCmdContext ctx, Map termMap, Services services) { List result = new ArrayList<>(); // Push the current branch context @@ -119,8 +135,13 @@ private static List renderProofCmd(ProofCmdContext ctx) { Map named = new HashMap<>(); List positional = new ArrayList<>(); for (ProofArgContext argContext : ctx.proofArg()) { - // FIXME: actually render the argument value. This is just a placeholder - var value = argContext.expression().getText(); + Object value; + JmlParser.ExpressionContext exp = argContext.expression(); + if(isStringLiteral(exp)) { + value = StringUtil.stripQuotes(exp.getText()); + } else { + value = termMap.get(exp); + } if (argContext.argLabel != null) { named.put(argContext.argLabel.getText(), value); } else { @@ -133,7 +154,7 @@ private static List renderProofCmd(ProofCmdContext ctx) { if(!ctx.proofCmd().isEmpty()) { result.add(new ScriptCommandAst("branches", Map.of("child", 0), List.of("select"))); for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext)); + result.addAll(renderProofCmd(proofCmdContext, termMap, services)); } } @@ -141,7 +162,7 @@ private static List renderProofCmd(ProofCmdContext ctx) { for (ProofCmdCaseContext pcase : ctx.proofCmdCase()) { result.add(new ScriptCommandAst("branches", Map.of("name", pcase.label), List.of("select"))); for (ProofCmdContext proofCmdContext : pcase.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext)); + result.addAll(renderProofCmd(proofCmdContext, termMap, services)); } } @@ -150,4 +171,8 @@ private static List renderProofCmd(ProofCmdContext ctx) { return result; } + + 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/nparser/KeyAst.java b/key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java index 85e2e7918c5..0f712c5f01d 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 @@ -246,7 +246,7 @@ private static ImmutableList collectTerms(JmlParser.ProofCmdC ImmutableList result = ImmutableList.of(); for (JmlParser.ProofArgContext arg : cmd.proofArg()) { JmlParser.ExpressionContext exp = arg.expression(); - if (exp != null && exp.start.getType() != JmlParser.STRING_LITERAL) { + if (exp != null) { result = result.prepend(exp); } } @@ -271,7 +271,7 @@ private static ImmutableList collectTerms(JmlParser.ProofCmdC public @NonNull ImmutableList collectTerms() { ImmutableList result = ImmutableList.of(); for (JmlParser.ProofCmdContext cmd : ctx.proofCmd()) { - result.prepend(collectTerms(cmd)); + result = result.prepend(collectTerms(cmd)); } return result.reverse(); } 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 index 4e47d1bb3ab..a54fcb75d02 100644 --- 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 @@ -1,9 +1,11 @@ package de.uka.ilkd.key.scripts; +import de.uka.ilkd.key.scripts.meta.Argument; import de.uka.ilkd.key.scripts.meta.Option; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Node; import de.uka.ilkd.key.proof.Proof; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Deque; @@ -100,10 +102,10 @@ public String getName() { public static class Parameters { /** A formula defining the goal to select */ - @Option(value = "#2") + @Argument public String mode; @Option(value = "branch") - public String branch; + public @Nullable String branch; @Option(value = "child") public int child; } 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 e32b7602b1f..b206d18a0bf 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 @@ -19,8 +19,7 @@ import de.uka.ilkd.key.strategy.Strategy; 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() { @@ -31,8 +30,13 @@ 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(); @@ -45,12 +49,9 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup : StrategyProperties.OSS_OFF); Strategy.updateStrategySettings(proof, newProps); OneStepSimplifier.refreshOSS(proof); - } else if (args.proofSteps != null) { - state.setMaxAutomaticSteps(args.proofSteps); - } else if (args.key != null) { - newProps.setProperty(args.key, args.value); - updateStrategySettings(newProps); - } else if (args.stackAction != null) { + } + + if (args.stackAction != null) { Stack stack = (Stack) state.getUserData("settingsStack"); if (stack == null) { stack = new Stack<>(); @@ -62,15 +63,14 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup break; case "pop": // TODO sensible error if empty - newProps = stack.pop(); - updateStrategySettings(newProps); + var resetProps = stack.pop(); + updateStrategySettings(state, resetProps); break; default: throw new IllegalArgumentException("stack must be either push or pop."); } - } else { - throw new IllegalArgumentException("You have to set oss, steps, stack, or key and value."); } + if (args.proofSteps != null) { state.setMaxAutomaticSteps(args.proofSteps); } @@ -147,7 +147,7 @@ public static class Parameters { @OptionalVarargs public Map settings = HashMap.newHashMap(0); - @Option(value = "stack", required = false) - public String stackAction; + @Option(value = "stack") + public @Nullable String stackAction; } } 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.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..27f0b1f7ecf 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; + } } From c00284e69f1b2291b115de8201cd12518e99526e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 21:16:26 +0200 Subject: [PATCH 07/77] adapting to new Script AST nodes --- .../src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e5edea309a0..85ba49f1b6e 100644 --- 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 @@ -148,7 +148,7 @@ private static List renderProofCmd(ProofCmdContext ctx, Map Date: Mon, 8 Sep 2025 21:46:49 +0200 Subject: [PATCH 08/77] renaming AssertCommand to FailUnlessCommand --- .../{AssertCommand.java => FailUnlessCommand.java} | 11 ++++------- .../de.uka.ilkd.key.scripts.ProofScriptCommand | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) rename key.core/src/main/java/de/uka/ilkd/key/scripts/{AssertCommand.java => FailUnlessCommand.java} (80%) 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/FailUnlessCommand.java similarity index 80% rename from key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java rename to key.core/src/main/java/de/uka/ilkd/key/scripts/FailUnlessCommand.java index 287f892cb49..2d2616e2fb1 100755 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/FailUnlessCommand.java @@ -8,18 +8,18 @@ import de.uka.ilkd.key.scripts.meta.Option; /** - * Halts the script if some condition is not met. + * 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 AssertCommand extends AbstractCommand { +public class FailUnlessCommand extends AbstractCommand { /** * Instantiates a new assert command. */ - public AssertCommand() { + public FailUnlessCommand() { super(Parameters.class); } @@ -49,10 +49,7 @@ public String getName() { 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". + Note: This command was called "assert" originally. """) public static class Parameters { /** 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 1ad5950eb3a..eb93d691e78 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 @@ -28,7 +28,7 @@ de.uka.ilkd.key.scripts.SkipCommand 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.FailUnlessCommand de.uka.ilkd.key.scripts.RewriteCommand de.uka.ilkd.key.scripts.AllCommand de.uka.ilkd.key.scripts.HideCommand From e6c2ed9c6bf70a3060567030408f34078a2016e9 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 8 Sep 2025 21:47:09 +0200 Subject: [PATCH 09/77] correcting the grammar for nested \by clauses --- key.core/src/main/antlr4/JmlParser.g4 | 2 +- .../de/uka/ilkd/key/speclang/njml/JmlFacade.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index fe632eef7d5..72c7bca51a8 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -211,7 +211,7 @@ assert_statement: (ASSERT expression | UNREACHABLE) (SEMI_TOPLEVEL | assertionPr assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; proofCmd: cmd=IDENT ( proofArg )* - ( SEMI | BY proofCmd | LBRACE (proofCmd+ | proofCmdCase+) RBRACE ) + ( SEMI | BY ( proofCmd | LBRACE (proofCmd+ | proofCmdCase+) RBRACE )) ; proofCmdCase: CASE ( label=STRING_LITERAL )? COLON ( proofCmd )* 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 0099c13e0bf..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 @@ -10,10 +10,7 @@ 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; /** @@ -116,9 +113,15 @@ private static ParserRuleContext getExpressionContext(JmlLexer lexer) { 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()); - var parser = createParser(createLexer(input)); + 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)); } From dc898e87da251f929e58e741d1706c023318d8e4 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 10 Sep 2025 19:10:12 +0200 Subject: [PATCH 10/77] improving the value injector --- .../java/de/uka/ilkd/key/proof/Proof.java | 18 ++++++- .../uka/ilkd/key/scripts/AssertCommand.java | 29 +++++++++++ .../meta/UnknownArgumentException.java | 15 ++++++ .../ilkd/key/scripts/meta/ValueInjector.java | 51 +++++++++++++++---- .../key_project/util/java/IntegerUtil.java | 24 +++++++++ 5 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/meta/UnknownArgumentException.java 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/scripts/AssertCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java new file mode 100644 index 00000000000..01867794b05 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java @@ -0,0 +1,29 @@ +/* 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("use"); + node.proof().getGoal(node.child(1)).setBranchLabel("show"); + } +} 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..957d9cdbbd0 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/UnknownArgumentException.java @@ -0,0 +1,15 @@ +/* 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 { + 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 94e95310a9c..98235875574 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 @@ -12,6 +12,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.key_project.util.java.IntegerUtil; /** * @author Alexander Weigl @@ -46,6 +47,10 @@ private record ConverterKey( Class source, Class target) { } + interface VerifyableParameters { + void verifyParameters() throws IllegalArgumentException; + } + /** * 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,35 @@ 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 argument %s (with value %s) was provided. For command class: '%s'", + unhandled.get(), + arguments.namedArgs().get(unhandled.get()), + obj.getClass().getName())); + } + + Optional unhandledPos = IntegerUtil.indexRangeOf(arguments.positionalArgs()) + .stream() + .filter(it -> !handledOptions.contains(it)) + .findAny(); + if(unhandledPos.isPresent()) { + long count = handledOptions.stream().filter(it -> it instanceof Integer).count(); + throw new UnknownArgumentException(String.format( + "Unexpected positional argument at index %d was provided. " + + "Expected (at most) %d positional arguments. For command class: '%s'", + unhandledPos.get(), count, obj.getClass().getName())); } return obj; @@ -149,19 +176,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,15 +205,16 @@ 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() @@ -196,8 +227,8 @@ private void injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, Ob }); // 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); } + handled = List.of(meta.getName()); } try { @@ -216,7 +247,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) @@ -309,7 +340,7 @@ public void addConverter(Converter conv) { */ @SuppressWarnings("unchecked") public @Nullable Converter getConverter(Class ret, Class arg) { - if (ret == arg) { + if (ret.isAssignableFrom(arg)) { return (T it) -> (R) it; } return (Converter) converters.get(new ConverterKey<>(ret, arg)); 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..b15033ed43d 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,25 @@ 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(); + } } From 11672e59b772ffaff41a605072a35f9ee4dafdfa Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 10 Sep 2025 19:11:38 +0200 Subject: [PATCH 11/77] renaming a deprecated command "assert" --> "assertOpenGoals" --- .../{FailUnlessCommand.java => AssertOpenGoalsCommand.java} | 6 +++--- .../services/de.uka.ilkd.key.scripts.ProofScriptCommand | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) rename key.core/src/main/java/de/uka/ilkd/key/scripts/{FailUnlessCommand.java => AssertOpenGoalsCommand.java} (92%) diff --git a/key.core/src/main/java/de/uka/ilkd/key/scripts/FailUnlessCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java similarity index 92% rename from key.core/src/main/java/de/uka/ilkd/key/scripts/FailUnlessCommand.java rename to key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java index 2d2616e2fb1..ba84205242e 100755 --- a/key.core/src/main/java/de/uka/ilkd/key/scripts/FailUnlessCommand.java +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java @@ -14,12 +14,12 @@ * * @author lanzinger */ -public class FailUnlessCommand extends AbstractCommand { +public class AssertOpenGoalsCommand extends AbstractCommand { /** * Instantiates a new assert command. */ - public FailUnlessCommand() { + public AssertOpenGoalsCommand() { super(Parameters.class); } @@ -39,7 +39,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup @Override public String getName() { - return "assert"; + return "assertOpenGoals"; } /** 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 eb93d691e78..091c21e9038 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,7 @@ 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.AssertCommand de.uka.ilkd.key.scripts.SetCommand de.uka.ilkd.key.scripts.SetEchoCommand de.uka.ilkd.key.scripts.SetFailOnClosedCommand @@ -28,7 +29,7 @@ de.uka.ilkd.key.scripts.SkipCommand 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.FailUnlessCommand +de.uka.ilkd.key.scripts.AssertOpenGoalsCommand de.uka.ilkd.key.scripts.RewriteCommand de.uka.ilkd.key.scripts.AllCommand de.uka.ilkd.key.scripts.HideCommand From a0a225a2893abe49edfe6884dd3e599ea54970ee Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 12 Sep 2025 11:58:11 +0200 Subject: [PATCH 12/77] spotless --- .../ilkd/key/java/Recoder2KeYConverter.java | 3 +- .../ilkd/key/java/recoderext/JmlAssert.java | 7 +- .../ilkd/key/java/statement/JmlAssert.java | 23 +++---- .../ilkd/key/macros/ApplyScriptsMacro.java | 53 ++++++++++----- .../ilkd/key/macros/ScriptAwareAutoMacro.java | 3 + .../uka/ilkd/key/macros/ScriptAwareMacro.java | 3 + .../java/de/uka/ilkd/key/nparser/KeyAst.java | 6 +- .../de/uka/ilkd/key/rule/JmlAssertRule.java | 3 +- .../uka/ilkd/key/scripts/AssertCommand.java | 10 +-- .../uka/ilkd/key/scripts/BranchesCommand.java | 66 +++++++++++-------- .../de/uka/ilkd/key/scripts/SetCommand.java | 12 ++-- .../ilkd/key/scripts/meta/ValueInjector.java | 24 +++---- .../TextualJMLAssertStatement.java | 4 +- .../key/speclang/njml/TextualTranslator.java | 4 +- .../key_project/util/java/IntegerUtil.java | 6 +- .../org/key_project/util/java/StringUtil.java | 4 +- 16 files changed, 137 insertions(+), 94 deletions(-) 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 ba9ae8c50be..8b140dcf69c 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,8 @@ 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(), ja.getAssertionProof(), positionInfo(ja)); + return new JmlAssert(ja.getKind(), ja.getCondition(), ja.getAssertionProof(), + positionInfo(ja)); } // ------------------- declaration --------------------- 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 2e84cfd5882..ebbce6b73e7 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 @@ -5,9 +5,7 @@ import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; -import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement.Kind; -import de.uka.ilkd.key.speclang.njml.JmlParser.AssertionProofContext; -import de.uka.ilkd.key.speclang.njml.LabeledParserRuleContext; + import org.jspecify.annotations.Nullable; import recoder.java.ProgramElement; import recoder.java.SourceVisitor; @@ -42,7 +40,8 @@ public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression conditio this(kind, condition, null); } - public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, KeyAst.@Nullable JMLProofScript assertionProof) { + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + KeyAst.@Nullable JMLProofScript assertionProof) { this.kind = kind; this.condition = condition; this.assertionProof = assertionProof; 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 bc139142a83..3959fb0079d 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 @@ -3,24 +3,20 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.java.statement; +import java.util.Objects; + 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.JTerm; +import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLAssertStatement; -import de.uka.ilkd.key.speclang.njml.LabeledParserRuleContext; + +import org.key_project.util.ExtList; +import org.key_project.util.collection.ImmutableList; + import org.antlr.v4.runtime.ParserRuleContext; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import org.key_project.util.ExtList; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import de.uka.ilkd.key.nparser.KeyAst; -import org.key_project.util.collection.ImmutableList; -import org.key_project.util.collection.Immutables; /** * A JML assert statement. @@ -54,7 +50,8 @@ public class JmlAssert extends JavaStatement { * @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, KeyAst.@Nullable JMLProofScript assertionProof, + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + KeyAst.@Nullable JMLProofScript assertionProof, PositionInfo positionInfo) { super(positionInfo); this.kind = kind; @@ -193,7 +190,7 @@ public void visit(Visitor v) { */ public @NonNull ImmutableList collectTerms() { ImmutableList result = ImmutableList.of(); - if(assertionProof != null) { + if (assertionProof != null) { result = result.prepend(assertionProof.collectTerms()); } result = result.prepend(condition.ctx); 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 index 85ba49f1b6e..3fc280a059e 100644 --- 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 @@ -1,5 +1,10 @@ +/* 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 de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.control.UserInterfaceControl; import de.uka.ilkd.key.java.JavaTools; @@ -20,17 +25,16 @@ 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 org.antlr.v4.runtime.ParserRuleContext; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; -import org.key_project.logic.op.Function; + import org.key_project.prover.engine.ProverTaskListener; 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 java.util.*; +import org.antlr.v4.runtime.ParserRuleContext; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; public class ApplyScriptsMacro extends AbstractProofMacro { @@ -56,7 +60,8 @@ public String getDescription() { } @Override - public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, PosInOccurrence posInOcc) { + public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, + PosInOccurrence posInOcc) { return fallBackMacro.canApplyTo(proof, goals, posInOcc) || goals.exists(g -> getScript(g) != null); } @@ -92,8 +97,10 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, } KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); Map termMap = getTermMap(jmlAssert, proof.getServices()); - List renderedProof = renderProof(proofScript, termMap, proof.getServices()); + List renderedProof = + renderProof(proofScript, termMap, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); + addConverters(pse, proof.getServices()); System.out.println("---- Script"); System.out.println(renderedProof); pse.execute((AbstractUserInterfaceControl) uic, proof); @@ -101,23 +108,31 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, return new ProofMacroFinishedInfo(this, proof); } + private void addConverters(ProofScriptEngine pse, Services services) { + // pse. + + } + private Map getTermMap(JmlAssert jmlAssert, 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); + 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 = jmlspec.terms().tail(); ImmutableList jmlExprs = jmlAssert.collectTerms().tail(); Map result = new IdentityHashMap<>(); assert terms.size() == jmlExprs.size(); - for(int i = 0; i < terms.size(); i++) { + for (int i = 0; i < terms.size(); i++) { // TODO build a map from jmlExprs.get(i) to terms.get(i) result.put(jmlExprs.get(i), terms.get(i)); } return result; } - private static List renderProof(KeyAst.JMLProofScript script, Map termMap, Services services) { + private static List renderProof(KeyAst.JMLProofScript script, + Map termMap, Services services) { List result = new ArrayList<>(); for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { result.addAll(renderProofCmd(proofCmdContext, termMap, services)); @@ -125,7 +140,8 @@ private static List renderProof(KeyAst.JMLProofScript script, return result; } - private static List renderProofCmd(ProofCmdContext ctx, Map termMap, Services services) { + private static List renderProofCmd(ProofCmdContext ctx, + Map termMap, Services services) { List result = new ArrayList<>(); // Push the current branch context @@ -137,7 +153,7 @@ private static List renderProofCmd(ProofCmdContext ctx, Map renderProofCmd(ProofCmdContext ctx, Map renderProofCmd(ProofCmdContext ctx, Map collectTerms(JmlParser.ProofCmdContext cmd) { + private static ImmutableList collectTerms( + JmlParser.ProofCmdContext cmd) { ImmutableList result = ImmutableList.of(); for (JmlParser.ProofArgContext arg : cmd.proofArg()) { JmlParser.ExpressionContext exp = arg.expression(); 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 47dcc48c86b..f5651c14dac 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 @@ -133,7 +133,8 @@ public IBuiltInRuleApp createApp(PosInOccurrence occurrence, TermServices servic final MethodFrame frame = JavaTools.getInnermostMethodFrame(target.javaBlock(), services); final JTerm self = MiscTools.getSelfTerm(frame, services); - final SpecificationRepository.JmlStatementSpec spec = services.getSpecificationRepository().getStatementSpec(jmlAssert); + final SpecificationRepository.JmlStatementSpec spec = + services.getSpecificationRepository().getStatementSpec(jmlAssert); if (spec == null) { throw new RuleAbortException( 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 index 01867794b05..b9a96d91c0d 100644 --- 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 @@ -6,10 +6,12 @@ /** * 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 + * 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.) + * (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 { @@ -23,7 +25,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup var args = state().getValueInjector().inject(new Parameters(), arguments); var node = state().getFirstOpenAutomaticGoal().node(); execute(state(), args); - node.proof().getGoal(node.child(0)).setBranchLabel("use"); - node.proof().getGoal(node.child(1)).setBranchLabel("show"); + 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/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java index a54fcb75d02..35853fe5e91 100644 --- 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 @@ -1,12 +1,8 @@ +/* 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.Argument; -import de.uka.ilkd.key.scripts.meta.Option; -import de.uka.ilkd.key.proof.Goal; -import de.uka.ilkd.key.proof.Node; -import de.uka.ilkd.key.proof.Proof; -import org.jspecify.annotations.Nullable; - import java.util.ArrayList; import java.util.Deque; import java.util.Iterator; @@ -15,6 +11,14 @@ 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.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Option; + +import org.jspecify.annotations.Nullable; + public class BranchesCommand extends AbstractCommand { public BranchesCommand() { super(Parameters.class); @@ -31,30 +35,34 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup } switch (args.mode) { - case "push": - Node node = state.getFirstOpenAutomaticGoal().node(); - // this is the first goal. The parent is the decision point - node = node.parent(); - 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; - default: - throw new ScriptException(); + 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; + default: + throw new ScriptException(); } } + private void ensureSingleGoal() { + state. + } + private Goal findGoalByName(Node root, String branch) throws ScriptException { Iterator it = root.childrenIterator(); List knownBranchLabels = new ArrayList<>(); @@ -107,7 +115,7 @@ public static class Parameters { @Option(value = "branch") public @Nullable String branch; @Option(value = "child") - public int child; + public @Nullable Integer child; } } 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 b206d18a0bf..dfcd9026e6c 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,7 +6,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.Properties; import java.util.Stack; import de.uka.ilkd.key.proof.Goal; @@ -19,6 +18,7 @@ import de.uka.ilkd.key.strategy.Strategy; import de.uka.ilkd.key.strategy.StrategyFactory; import de.uka.ilkd.key.strategy.StrategyProperties; + import org.jspecify.annotations.Nullable; public class SetCommand extends AbstractCommand { @@ -30,8 +30,9 @@ 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)."); + if (args.settings.isEmpty()) { + throw new IllegalArgumentException( + "You have to set oss, steps, stack, or key(s) and value(s)."); } args.settings.remove("oss"); @@ -52,12 +53,13 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup } if (args.stackAction != null) { - Stack stack = (Stack) state.getUserData("settingsStack"); + Stack stack = + (Stack) state.getUserData("settingsStack"); if (stack == null) { stack = new Stack<>(); state.putUserData("settingsStack", stack); } - switch(args.stackAction) { + switch (args.stackAction) { case "push": stack.push(newProps.clone()); break; 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 98235875574..f5881b9e57a 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,10 +9,11 @@ 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; -import org.key_project.util.java.IntegerUtil; /** * @author Alexander Weigl @@ -137,24 +138,24 @@ public T inject(T obj, ScriptCommandAst arguments) Optional unhandled = arguments.namedArgs().keySet().stream() .filter(it -> !handledOptions.contains(it)) .findAny(); - if(unhandled.isPresent()) { + if (unhandled.isPresent()) { throw new UnknownArgumentException(String.format( - "Unknown argument %s (with value %s) was provided. For command class: '%s'", - unhandled.get(), - arguments.namedArgs().get(unhandled.get()), - obj.getClass().getName())); + "Unknown argument %s (with value %s) was provided. For command class: '%s'", + unhandled.get(), + arguments.namedArgs().get(unhandled.get()), + obj.getClass().getName())); } Optional unhandledPos = IntegerUtil.indexRangeOf(arguments.positionalArgs()) .stream() .filter(it -> !handledOptions.contains(it)) .findAny(); - if(unhandledPos.isPresent()) { + if (unhandledPos.isPresent()) { long count = handledOptions.stream().filter(it -> it instanceof Integer).count(); throw new UnknownArgumentException(String.format( - "Unexpected positional argument at index %d was provided. " + - "Expected (at most) %d positional arguments. For command class: '%s'", - unhandledPos.get(), count, obj.getClass().getName())); + "Unexpected positional argument at index %d was provided. " + + "Expected (at most) %d positional arguments. For command class: '%s'", + unhandledPos.get(), count, obj.getClass().getName())); } return obj; @@ -336,7 +337,8 @@ public void addConverter(Converter conv) { * @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. + * @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) { 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 192d8595f14..79b41ec577b 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 @@ -4,9 +4,11 @@ package de.uka.ilkd.key.speclang.jml.pretranslation; import de.uka.ilkd.key.nparser.KeyAst; + +import org.key_project.util.collection.ImmutableSLList; + import org.antlr.v4.runtime.RuleContext; import org.jspecify.annotations.Nullable; -import org.key_project.util.collection.ImmutableSLList; /** * A JML assert/assume statement. 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 28ec1c970f7..1158d544fb2 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 @@ -522,8 +522,8 @@ public Object visitAssume_statement(JmlParser.Assume_statementContext ctx) { public Object visitAssert_statement(JmlParser.Assert_statementContext ctx) { TextualJMLAssertStatement b = new TextualJMLAssertStatement(TextualJMLAssertStatement.Kind.ASSERT, - new KeyAst.Expression(ctx.expression()), - KeyAst.JMLProofScript.fromContext(ctx.assertionProof())); + new KeyAst.Expression(ctx.expression()), + KeyAst.JMLProofScript.fromContext(ctx.assertionProof())); constructs = constructs.append(b); return null; } 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 b15033ed43d..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 @@ -33,7 +33,8 @@ public static int factorial(int n) { } /** - * Creates a list of integers from {@code 0} (inclusive) to the size of the given collection (exclusive). + * 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()); @@ -47,7 +48,8 @@ private static List rangeUntil(int size) { } /** - * Creates a list of integers from {@code from} (inclusive) to {@code untilExclusive} (exclusive). + * 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 27f0b1f7ecf..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 @@ -562,10 +562,10 @@ public static String removeEmptyLines(String string) { * present. */ public static String stripQuotes(String text) { - if(text.length() >= 2 && text.startsWith("\"") && text.endsWith("\"")) { + if (text.length() >= 2 && text.startsWith("\"") && text.endsWith("\"")) { return text.substring(1, text.length() - 1); } - if(text.length() >= 2 && text.startsWith("'") && text.endsWith("'")) { + if (text.length() >= 2 && text.startsWith("'") && text.endsWith("'")) { return text.substring(1, text.length() - 1); } return text; From c506ab604fcd97cda3909a4cbe94f14a79071ce8 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 24 Sep 2025 18:05:53 +0200 Subject: [PATCH 13/77] working version of scripts --- .../ilkd/key/macros/ApplyScriptsMacro.java | 47 ++++++++++++------ .../uka/ilkd/key/scripts/AssertCommand.java | 4 +- .../uka/ilkd/key/scripts/BranchesCommand.java | 49 ++++++++++++++++--- .../ilkd/key/scripts/ProofScriptEngine.java | 3 ++ .../key/scripts/meta/InjectionException.java | 9 ++++ .../ilkd/key/scripts/meta/ValueInjector.java | 10 +++- 6 files changed, 97 insertions(+), 25 deletions(-) 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 index 3fc280a059e..055ece5053d 100644 --- 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 @@ -4,6 +4,7 @@ package de.uka.ilkd.key.macros; import java.util.*; +import java.util.stream.Collectors; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; import de.uka.ilkd.key.control.UserInterfaceControl; @@ -26,6 +27,7 @@ import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdCaseContext; import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdContext; +import org.key_project.logic.Term; import org.key_project.prover.engine.ProverTaskListener; import org.key_project.prover.rules.RuleApp; import org.key_project.prover.sequent.PosInOccurrence; @@ -35,9 +37,13 @@ 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 ProofMacro fallBackMacro; public ApplyScriptsMacro(ProofMacro fallBackMacro) { @@ -81,6 +87,15 @@ private static JmlAssert getScript(Goal goal) { return null; } + private static @Nullable JTerm getUpdate(Goal goal) { + RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); + Term appliedOn = ruleApp.posInOccurrence().subTerm(); + if(appliedOn.op() instanceof UpdateApplication) { + return UpdateApplication.getUpdate((JTerm) appliedOn); + } + return null; + } + @Override public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, ImmutableList goals, PosInOccurrence posInOcc, ProverTaskListener listener) @@ -97,21 +112,19 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, } KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); Map termMap = getTermMap(jmlAssert, proof.getServices()); + JTerm update = getUpdate(goal); List renderedProof = - renderProof(proofScript, termMap, proof.getServices()); + renderProof(proofScript, termMap, update, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); - addConverters(pse, proof.getServices()); - System.out.println("---- Script"); - System.out.println(renderedProof); + LOGGER.debug("---- Script"); + LOGGER.debug(renderedProof.stream().map(ScriptCommandAst::asCommandLine).collect(Collectors.joining("\n"))); + LOGGER.debug("---- End Script"); + pse.execute((AbstractUserInterfaceControl) uic, proof); } return new ProofMacroFinishedInfo(this, proof); } - private void addConverters(ProofScriptEngine pse, Services services) { - // pse. - - } private Map getTermMap(JmlAssert jmlAssert, Services services) { SpecificationRepository.@Nullable JmlStatementSpec jmlspec = @@ -131,17 +144,17 @@ private Map getTermMap(JmlAssert jmlAssert, Services s return result; } - private static List renderProof(KeyAst.JMLProofScript script, - Map termMap, Services services) { + private static List renderProof(KeyAst.JMLProofScript script, + Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext, termMap, services)); + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); } return result; } private static List renderProofCmd(ProofCmdContext ctx, - Map termMap, Services services) { + Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); // Push the current branch context @@ -157,6 +170,10 @@ private static List renderProofCmd(ProofCmdContext ctx, value = StringUtil.stripQuotes(exp.getText()); } else { value = termMap.get(exp); + if(update != null) { + // Wrap in update application if an update is present + value = services.getTermBuilder().apply(update, (JTerm)value); + } } if (argContext.argLabel != null) { named.put(argContext.argLabel.getText(), value); @@ -169,9 +186,9 @@ private static List renderProofCmd(ProofCmdContext ctx, // handle proofCmd if present if (!ctx.proofCmd().isEmpty()) { - result.add(new ScriptCommandAst("branches", Map.of("child", 0), List.of("select"))); + result.add(new ScriptCommandAst("branches", Map.of(), List.of("single"))); for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext, termMap, services)); + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); } } @@ -181,7 +198,7 @@ private static List renderProofCmd(ProofCmdContext ctx, result.add(new ScriptCommandAst("branches", Map.of("branch", label), List.of("select"))); for (ProofCmdContext proofCmdContext : pcase.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext, termMap, services)); + result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); } } 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 index b9a96d91c0d..2bc1f27d91e 100644 --- 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 @@ -25,7 +25,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup 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"); + // 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/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java index 35853fe5e91..33500c34591 100644 --- 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 @@ -11,19 +11,31 @@ import java.util.Optional; import java.util.Stack; +import de.uka.ilkd.key.logic.op.SortDependingFunction; 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.InjectionException; import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.scripts.meta.ValueInjector; import org.jspecify.annotations.Nullable; +import org.key_project.prover.rules.tacletbuilder.TacletGoalTemplate; +import org.key_project.util.collection.ImmutableList; 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); @@ -34,6 +46,10 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup state.putUserData("_branchStack", stack); } + if(args.mode == null) { + throw new ScriptException("For 'branches', a mode must be specified"); + } + switch (args.mode) { case "push": ensureSingleGoal(); @@ -53,14 +69,38 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup goal = findGoalByName(root, args.branch); } state.setGoal(goal); + 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(); + throw new ScriptException("Unknown mode " + args.mode + " for the 'branches' command" ); } } private void ensureSingleGoal() { - state. + //state. } private Goal findGoalByName(Node root, String branch) throws ScriptException { @@ -103,11 +143,6 @@ private Node findNodeByNumber(Proof proof, int serial) throws ScriptException { throw new ScriptException(); } - @Override - public String getName() { - return "branches"; - } - public static class Parameters { /** A formula defining the goal to select */ @Argument 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 59a0eb98bf2..f0a611a5826 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 @@ -180,6 +180,9 @@ 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", 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/ValueInjector.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java index f5881b9e57a..4f4d93eee73 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 @@ -49,7 +49,7 @@ private record ConverterKey( } interface VerifyableParameters { - void verifyParameters() throws IllegalArgumentException; + void verifyParameters() throws IllegalArgumentException, InjectionException; } /** @@ -158,6 +158,14 @@ public T inject(T obj, ScriptCommandAst arguments) unhandledPos.get(), count, obj.getClass().getName())); } + if(obj instanceof VerifyableParameters vp) { + try { + vp.verifyParameters(); + } catch(IllegalArgumentException e) { + throw new InjectionException(e); + } + } + return obj; } From 731ac74682a956678c64ed2c4d36f33de1f71e63 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 24 Sep 2025 22:09:03 +0200 Subject: [PATCH 14/77] Position info for scripts with url=null, run scripted goals before other goals, print position manual cherry-pick From 547ce291d230a59b3abf4596ccede2e6a64aed75 Mon Sep 17 00:00:00 2001 From: Julian Wiesler Date: Wed, 22 Feb 2023 13:57:21 +0100 Subject: [PATCH] Position info for scripts with url=null, run scripted goals before other goals, print position information --- .../ilkd/key/macros/ApplyScriptsMacro.java | 40 +++++++++++++------ .../java/de/uka/ilkd/key/parser/Location.java | 12 ++++++ .../ilkd/key/scripts/ProofScriptEngine.java | 2 +- .../uka/ilkd/key/scripts/ScriptException.java | 4 +- 4 files changed, 43 insertions(+), 15 deletions(-) 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 index 055ece5053d..c32ab6e43f9 100644 --- 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 @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.macros; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -17,11 +18,14 @@ import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; 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.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.speclang.njml.JmlParser; import de.uka.ilkd.key.speclang.njml.JmlParser.ProofArgContext; import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdCaseContext; @@ -29,6 +33,7 @@ import org.key_project.logic.Term; 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; @@ -52,35 +57,35 @@ public ApplyScriptsMacro(ProofMacro fallBackMacro) { @Override public String getName() { - return "null"; + return "Apply scripts macro"; } @Override public String getCategory() { - return "null"; + return null; } @Override public String getDescription() { - return "null"; + return "Apply scripts"; } @Override public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, PosInOccurrence posInOcc) { return fallBackMacro.canApplyTo(proof, goals, posInOcc) - || goals.exists(g -> getScript(g) != null); + || goals.exists(g -> getJmlAssert(g.node()) != null); } - private static JmlAssert getScript(Goal goal) { - RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); + 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) { + if (activeStatement instanceof JmlAssert jmlAssert && jmlAssert.getAssertionProof() != null) { return jmlAssert; } } @@ -99,17 +104,21 @@ private static JmlAssert getScript(Goal goal) { @Override public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, ImmutableList goals, PosInOccurrence posInOcc, ProverTaskListener listener) - throws InterruptedException, Exception { + throws Exception { + ArrayList laterGoals = new ArrayList<>(goals.size()); for (Goal goal : goals) { if (Thread.interrupted()) { throw new InterruptedException(); } - JmlAssert jmlAssert = getScript(goal); - if (jmlAssert == null || jmlAssert.getAssertionProof() == null) { - // no script found, use fallback macro - fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + + 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, proof.getServices()); JTerm update = getUpdate(goal); @@ -122,6 +131,13 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, pse.execute((AbstractUserInterfaceControl) uic, proof); } + 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(); + } + fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + } return new ProofMacroFinishedInfo(this, proof); } 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..e0f3d95c891 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 @@ -6,11 +6,13 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.Comparator; import java.util.Objects; 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 +70,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/scripts/ProofScriptEngine.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java index f0a611a5826..229fa298489 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 @@ -145,7 +145,7 @@ public void execute(AbstractUserInterfaceControl uiControl, List Date: Wed, 24 Sep 2025 22:25:07 +0200 Subject: [PATCH 15/77] Use AutoPilotPrepareProofMacro instead of FinishSymbolicExecutionMacro since it splits a lot better # Conflicts: # key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java --- .../src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java | 1 - .../src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 index c32ab6e43f9..d5b22dd4d3a 100644 --- 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 @@ -25,7 +25,6 @@ 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.speclang.njml.JmlParser; import de.uka.ilkd.key.speclang.njml.JmlParser.ProofArgContext; import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdCaseContext; 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 index 107c3cbd028..a2992ef312e 100644 --- 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 @@ -34,7 +34,7 @@ */ public class ScriptAwareMacro extends SequentialProofMacro { - private final ProofMacro autoMacro = new FinishSymbolicExecutionMacro(); + private final ProofMacro autoMacro = new AutoPilotPrepareProofMacro(); private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(new TryCloseMacro()); @Override From 8c416b12f8aa8faafc04dc9e37a8e9f21cb2c090 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 24 Sep 2025 22:30:55 +0200 Subject: [PATCH 16/77] working on improved script commands manual cherry-picking commit 886588af1c72cdbf86b7a624a4045c6b7fb6a4b2 Author: Mattias Ulbrich Date: Sun Feb 5 13:14:29 2023 +0100 --- .../main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java | 4 ++++ 1 file changed, 4 insertions(+) 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 index d5b22dd4d3a..683338ce92c 100644 --- 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 @@ -162,9 +162,13 @@ private Map getTermMap(JmlAssert jmlAssert, Services s private static List renderProof(KeyAst.JMLProofScript script, Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); + // Push current settings onto the settings stack + result.add(new ScriptCommandAst("set", Map.of("stack", "push"), 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; } From 5e15b3e7574208e6187160c26ccbbb56655cb049 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 26 May 2023 22:46:36 +0200 Subject: [PATCH 17/77] advancing the immutable list interface --- .../util/collection/ImmutableList.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) 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..17d19e63f0b 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(); + } } } From 5ae4c959adbc51a0625aaf86a02a695845ac5aa7 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 24 Sep 2025 23:04:01 +0200 Subject: [PATCH 18/77] reorganisation of proof script commands manually cherry-picked from old branch --- .../prover/impl/DepthFirstGoalChooser.java | 3 + .../uka/ilkd/key/scripts/AssertCommand.java | 62 +++++++++---------- .../de/uka/ilkd/key/scripts/CutCommand.java | 8 +++ .../de/uka/ilkd/key/scripts/LetCommand.java | 11 +++- .../de/uka/ilkd/key/scripts/RuleCommand.java | 28 +++------ ...de.uka.ilkd.key.scripts.ProofScriptCommand | 2 +- 6 files changed, 62 insertions(+), 52 deletions(-) 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/scripts/AssertCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertCommand.java index 2bc1f27d91e..a800bddd913 100644 --- 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,31 +1,31 @@ -/* 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"); - } -} +///* 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/CutCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/CutCommand.java index 454393b0fb8..e1809d37167 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 @@ -14,6 +14,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import java.util.List; + /** * The command object CutCommand has as scriptcommand name "cut" As parameters: a formula with the * id "#2" @@ -30,6 +32,12 @@ public String getName() { return "cut"; } + // From within JML scripts, "assert" is more common than "cut" + @Override + public List getAliases() { + return List.of(getName(), "assert"); + } + @Override public String getDocumentation() { return """ 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..25c3977ab0a 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 @@ -10,6 +10,7 @@ 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.ProofScriptArgument; import org.jspecify.annotations.NullMarked; @@ -32,13 +33,21 @@ /// * Apr,2025 (weigl): remove {@code force} in favor of {@code letf}. /// * Jan,2025 (weigl): add new parameter {@code force} to override bindings. @NullMarked +@Documentation(""" + 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 class LetCommand implements ProofScriptCommand { + @Override public List getArguments() { return List.of(); } - @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, EngineState stateMap) throws ScriptException, InterruptedException { 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 70592408954..5b94519673f 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 @@ -14,6 +14,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; @@ -58,23 +59,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 { @@ -241,7 +225,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"); } @@ -405,18 +389,23 @@ private List filterList(Parameters p, ImmutableList list) return matchingApps; } + @Documentation("Command that applies a calculus rule.") public static class Parameters { @Argument + @Documentation("Name of the rule to be applied.") public @MonotonicNonNull String rulename; @Option(value = "on") + @Documentation("Term on which the rule should be applied to (matching the 'find' clause of the rule).") public @Nullable JTerm on; @Option(value = "formula") + @Documentation("Top-level formula in which the term appears.") public @Nullable JTerm formula; @Option(value = "occ") - public @Nullable int occ = -1; + @Documentation("Occurrence number if more than one occurrence matches.") + public @Nullable Integer occ = -1; /** * Represents a part of a formula (may use Java regular expressions as long as supported by @@ -426,6 +415,7 @@ public static class Parameters { public @Nullable String matches = null; @OptionalVarargs(as = JTerm.class, prefix = "inst_") + @Documentation("Instantiations for schema variables used in the rule.") public Map instantiations = new HashMap<>(); } 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 091c21e9038..5b75cde6842 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,7 +7,7 @@ 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.AssertCommand +# 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 From a825b96d2121edaf74b08c694afe82942c203278 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 26 May 2023 22:46:58 +0200 Subject: [PATCH 19/77] advancing for scripts from JML --- .../ilkd/key/macros/ApplyScriptsMacro.java | 7 ++ .../uka/ilkd/key/scripts/AbstractCommand.java | 11 +- .../ilkd/key/scripts/ExpandDefCommand.java | 116 ++++++++++++++++++ .../key/scripts/SetFailOnClosedCommand.java | 3 + ...de.uka.ilkd.key.scripts.ProofScriptCommand | 1 + .../org/key_project/util/java/IOUtil.java | 31 +++++ 6 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/ExpandDefCommand.java 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 index 683338ce92c..b50790ed207 100644 --- 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 @@ -44,6 +44,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; + public class ApplyScriptsMacro extends AbstractProofMacro { private static final Logger LOGGER = LoggerFactory.getLogger(ApplyScriptsMacro.class); @@ -136,7 +138,9 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, throw new InterruptedException(); } fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + } + return new ProofMacroFinishedInfo(this, proof); } @@ -162,6 +166,9 @@ private Map getTermMap(JmlAssert jmlAssert, Services s private static List renderProof(KeyAst.JMLProofScript script, Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); + // Do not fail on open proofs + // TODO Migrate into SetCommand + result.add(new ScriptCommandAst("failonopen", Map.of(), List.of("off"))); // Push current settings onto the settings stack result.add(new ScriptCommandAst("set", Map.of("stack", "push"), List.of())); for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { 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..84648d2198e 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,16 @@ 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 +86,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 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..e45bbb003fb --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/ExpandDefCommand.java @@ -0,0 +1,116 @@ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.pp.LogicPrinter; +import de.uka.ilkd.key.proof.BuiltInRuleAppIndex; +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.proof.RuleAppIndex; +import de.uka.ilkd.key.rule.BuiltInRule; +import de.uka.ilkd.key.rule.FindTaclet; +import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.rule.MatchConditions; +import de.uka.ilkd.key.rule.NoFindTaclet; +import de.uka.ilkd.key.rule.NoPosTacletApp; +import de.uka.ilkd.key.rule.PosTacletApp; +import de.uka.ilkd.key.rule.TacletApp; +import de.uka.ilkd.key.scripts.meta.Option; +import org.jspecify.annotations.Nullable; +import org.key_project.logic.PosInTerm; +import org.key_project.logic.Term; +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.key_project.util.collection.ImmutableSLList; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +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"); + } + + g.apply(completions.head()); + } + + 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())); + } + + apps = apps.filter(it -> it instanceof PosTacletApp && it.posInOccurrence().subTerm().equals(p.on)); + + 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 Term on; + @Option(value = "occ") + public @Nullable Integer occ; + } + + 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/SetFailOnClosedCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SetFailOnClosedCommand.java index 34119c7b6de..85aeb15dbe6 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 @@ -15,7 +15,10 @@ * 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 public class SetFailOnClosedCommand extends AbstractCommand { public SetFailOnClosedCommand() { super(Parameters.class); 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 5b75cde6842..17a65504657 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 @@ -29,6 +29,7 @@ de.uka.ilkd.key.scripts.SkipCommand 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.ExpandDefCommand de.uka.ilkd.key.scripts.AssertOpenGoalsCommand de.uka.ilkd.key.scripts.RewriteCommand de.uka.ilkd.key.scripts.AllCommand 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..72b1b9d5c4b 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,37 @@ 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. * From 94dac2f79ba511462f8a885204370e3f6424ea3e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 26 May 2023 23:37:55 +0200 Subject: [PATCH 20/77] slight adaptation of the macro used for scripting --- .../src/main/java/de/uka/ilkd/key/macros/ScriptAwareMacro.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a2992ef312e..107c3cbd028 100644 --- 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 @@ -34,7 +34,7 @@ */ public class ScriptAwareMacro extends SequentialProofMacro { - private final ProofMacro autoMacro = new AutoPilotPrepareProofMacro(); + private final ProofMacro autoMacro = new FinishSymbolicExecutionMacro(); private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(new TryCloseMacro()); @Override From 798f2c3e29d154c4bae65fb066cbbb055961a4c1 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 2 Jun 2023 08:26:49 +0200 Subject: [PATCH 21/77] more script commands --- .../scripts/DependencyContractCommand.java | 107 ++++++++++++++++++ .../key/scripts/OneStepSimplifierCommand.java | 60 ++++++++++ ...de.uka.ilkd.key.scripts.ProofScriptCommand | 2 + 3 files changed, 169 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/DependencyContractCommand.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/OneStepSimplifierCommand.java 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..81e755d770e --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/DependencyContractCommand.java @@ -0,0 +1,107 @@ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.rule.UseDependencyContractApp; +import org.jspecify.annotations.Nullable; +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 java.util.ArrayList; +import java.util.List; + +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); + } + + public static class Parameters { + @Option(value = "on") + public JTerm on; + + @Option(value = "heap") + public @Nullable JTerm heap; + } +} 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..184116ca1f2 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/OneStepSimplifierCommand.java @@ -0,0 +1,60 @@ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.scripts.meta.Option; +import de.uka.ilkd.key.proof.Goal; +import de.uka.ilkd.key.rule.IBuiltInRuleApp; +import de.uka.ilkd.key.rule.OneStepSimplifierRuleApp; +import org.jspecify.annotations.Nullable; +import org.key_project.logic.PosInTerm; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.util.collection.ImmutableList; + +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(arguments.antecedent) { + for (SequentFormula sf : goal.sequent().antecedent()) { + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, new PosInOccurrence(sf, PosInTerm.getTopLevel(), true)); + for (IBuiltInRuleApp builtin : builtins) { + if (builtin instanceof OneStepSimplifierRuleApp) { + goal.apply(builtin); + } + } + } + } + + if(arguments.succedent) { + for (SequentFormula sf : goal.sequent().succedent()) { + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, new PosInOccurrence(sf, PosInTerm.getTopLevel(), false)); + for (IBuiltInRuleApp builtin : builtins) { + if (builtin instanceof OneStepSimplifierRuleApp) { + goal.apply(builtin); + } + } + } + } + } + + public static class Parameters { + @Option(value = "antecedent") + public @Nullable Boolean antecedent = Boolean.TRUE; + + @Option(value = "succedent") + public @Nullable Boolean succedent = Boolean.TRUE; + } +} 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 17a65504657..82b1c68cd8e 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 @@ -26,6 +26,8 @@ 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 From e1086d15a0e70aedba6f0fd4f2e1b59c11a50d1a Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 5 Jun 2023 09:19:30 +0200 Subject: [PATCH 22/77] improving/correcting script commands --- .../uka/ilkd/key/scripts/ExpandDefCommand.java | 17 ++++++++--------- .../de/uka/ilkd/key/scripts/SetCommand.java | 6 +++++- 2 files changed, 13 insertions(+), 10 deletions(-) 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 index e45bbb003fb..7eaff45ae7d 100644 --- 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 @@ -26,14 +26,6 @@ import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.prover.sequent.SequentFormula; import org.key_project.util.collection.ImmutableList; -import org.key_project.util.collection.ImmutableSLList; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; public class ExpandDefCommand extends AbstractCommand { @@ -59,7 +51,14 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte throw new ScriptException("Cannot complete the rule app"); } - g.apply(completions.head()); + 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 { 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 dfcd9026e6c..ea5a1201960 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 @@ -50,7 +50,11 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup : StrategyProperties.OSS_OFF); Strategy.updateStrategySettings(proof, newProps); OneStepSimplifier.refreshOSS(proof); - } + } + + if (args.proofSteps != null) { + state.setMaxAutomaticSteps(args.proofSteps); + } if (args.stackAction != null) { Stack stack = From 428e7d17eca9eecd25d70cba6010f83b1ff706a2 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 8 Jun 2023 14:47:20 +0200 Subject: [PATCH 23/77] settings for the auto command --- key.core/src/main/antlr4/JmlParser.g4 | 2 +- .../de/uka/ilkd/key/scripts/AutoCommand.java | 33 +++++++------------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index 72c7bca51a8..02904089f26 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -201,7 +201,7 @@ 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 | assertionProof); +assert_statement: (ASSERT expression | UNREACHABLE) (assertionProof SEMI_TOPLEVEL? | SEMI_TOPLEVEL); //breaks_clause: BREAKS expression; //continues_clause: CONTINUES expression; //returns_clause: RETURNS expression; 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..9dfa98d7358 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,9 +12,7 @@ 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.StrategyProperties; @@ -48,11 +46,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 +62,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 +72,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 @@ -134,7 +127,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; } @@ -171,7 +164,7 @@ private void setupFocussedBreakpointStrategy(final String maybeMatchesRegEx, @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 + 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 @@ -202,34 +195,30 @@ public static class Parameters { @Flag(value = "modelsearch") @Documentation("Enable model search. Better for some types of arithmetic problems. Sometimes a lot worse") - public boolean modelSearch; + public @Nullable Boolean modelSearch; @Flag(value = "expandQueries") @Documentation("Expand queries by modalities.") - public boolean expandQueries; + public @Nullable Boolean expandQueries; @Flag(value = "classAxioms") @Documentation(""" Enable class axioms. This expands model methods and fields and invariants quite eagerly. \ May lead to divergence.""") - public boolean classAxioms; + public @Nullable Boolean classAxioms; @Flag(value = "dependencies") @Documentation(""" Enable dependency reasoning. In modular reasoning, the value of symbols may stay the same, \ without that its definition is known. May be an enabler, may be a showstopper.""") - public boolean dependencies; - - public int getSteps() { - return maxSteps; - } + public @Nullable Boolean dependencies; } 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; From 64f6f7fbd32f0ab23896c559dd21208b2cfd5c19 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 9 Jun 2023 13:20:59 +0200 Subject: [PATCH 24/77] propagating strategy settings to the strategy to be used (refers to code available in SetCommand) --- key.core/src/main/java/de/uka/ilkd/key/scripts/SetCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ea5a1201960..50c9ff965a9 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 @@ -101,7 +101,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); From e4377b8ec7863fe76ba1404dc8cfacc847169461 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 15 Jun 2023 21:45:11 +0200 Subject: [PATCH 25/77] proof scripts: adding a CHEAT command --- .../de/uka/ilkd/key/scripts/CheatCommand.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java 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..c1f5222666d --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java @@ -0,0 +1,47 @@ +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 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; + +public class CheatCommand extends NoArgumentCommand { + private static final Taclet CHEAT_TACLET; + + static { + TacletApplPart applPart = new TacletApplPart(JavaDLSequentKit.getInstance().getEmptySequent(), + ApplicationRestriction.NONE, 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 String getDocumentation() { + return "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."; + } + + @Override + public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst ast, + EngineState state) + throws ScriptException, InterruptedException { + TacletApp app = NoPosTacletApp.createNoPosTacletApp(CHEAT_TACLET); + state.getFirstOpenAutomaticGoal().apply(app); + } +} From cdf60350c4d2094018a120f26649ea65cc9b4041 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 17 Jun 2023 20:24:37 +0200 Subject: [PATCH 26/77] updating a few of the script commands --- .../main/java/de/uka/ilkd/key/scripts/RuleCommand.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 5b94519673f..7f2ccabee0e 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 @@ -144,9 +144,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"); } @@ -244,7 +246,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."); @@ -363,7 +365,7 @@ 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<>(); for (TacletApp tacletApp : list) { if (tacletApp instanceof PosTacletApp pta) { From 5cad3f1e9dd7ce5b32644457e6bde793c4e730be Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 19 Jun 2023 10:17:34 +0200 Subject: [PATCH 27/77] introducing the script-aware prep macro --- .../ilkd/key/macros/ApplyScriptsMacro.java | 8 ++- .../uka/ilkd/key/macros/ScriptAwareMacro.java | 8 +-- .../ilkd/key/macros/ScriptAwarePrepMacro.java | 61 +++++++++++++++++++ .../de.uka.ilkd.key.macros.ProofMacro | 1 + 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwarePrepMacro.java 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 index b50790ed207..fb529ddb3ae 100644 --- 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 @@ -50,7 +50,7 @@ public class ApplyScriptsMacro extends AbstractProofMacro { private static final Logger LOGGER = LoggerFactory.getLogger(ApplyScriptsMacro.class); - private final ProofMacro fallBackMacro; + private final @Nullable ProofMacro fallBackMacro; public ApplyScriptsMacro(ProofMacro fallBackMacro) { this.fallBackMacro = fallBackMacro; @@ -74,7 +74,7 @@ public String getDescription() { @Override public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, PosInOccurrence posInOcc) { - return fallBackMacro.canApplyTo(proof, goals, posInOcc) + return fallBackMacro != null && fallBackMacro.canApplyTo(proof, goals, posInOcc) || goals.exists(g -> getJmlAssert(g.node()) != null); } @@ -137,7 +137,9 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, if (Thread.interrupted()) { throw new InterruptedException(); } - fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + if(fallBackMacro != null) { + fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); + } } 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 index 107c3cbd028..320e7d57c22 100644 --- 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 @@ -18,19 +18,19 @@ /** * This class captures a proof macro which is meant to fully automise KeY proof - * workflow. + * workflow if scripts are present in the JML code. * * It is experimental. * * It performs the following steps: *
    *
  1. Finish symbolic execution - *
  2. >Separate proof obligations" + - *
  3. Expand invariant definitions - *
  4. Try to close all proof obligations + *
  5. Apply macros + *
  6. Try to close provable goals *
* * @author mattias ulbrich + * @see ScriptAwarePrepMacro */ public class ScriptAwareMacro extends SequentialProofMacro { 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..f9d5c8dceac --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/ScriptAwarePrepMacro.java @@ -0,0 +1,61 @@ +// 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 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/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 9e59248bcfa..313dd07613b 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 @@ -31,3 +31,4 @@ 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 From bd0ec8572a7f70d791fa7dffa925320ab8894ef9 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 19 Jun 2023 14:36:11 +0200 Subject: [PATCH 28/77] issue dialog: skip spaces for squiggly lines --- key.ui/src/main/java/de/uka/ilkd/key/gui/IssueDialog.java | 3 +++ 1 file changed, 3 insertions(+) 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..032b47ca351 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++; From dfc97ce8c9d224876dbb1ea7a413c662a0ae1924 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 19 Jun 2023 15:17:31 +0200 Subject: [PATCH 29/77] value injector: towards reporting unknown arguments --- .../meta/UnknownArgumentException.java | 6 +++ .../key/scripts/meta/ValueInjectorTest.java | 41 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) 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 index 957d9cdbbd0..3cbd1035958 100644 --- 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 @@ -9,6 +9,12 @@ * @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/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..3ddbf9239c4 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,6 +58,38 @@ 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("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("#literal", "here goes the entire string..."); + args.put("i", "42"); + args.put("b", "true"); + args.put("var_21", "21"); + args.put("var_other", "otherString"); + ValueInjector.injection(new PPCommand(), pp, ast); + assertEquals("21", pp.varargs.get("21")); + assertEquals("otherString", pp.varargs.get("other")); + assertEquals(2, pp.varargs.size()); + } + @Test public void testInferScriptArguments() throws NoSuchFieldException { List meta = ArgumentsLifter.inferScriptArguments(PP.class); @@ -90,8 +122,7 @@ public void testInferScriptArguments() throws NoSuchFieldException { } @Test - public void testFlag() throws ConversionException, ArgumentRequiredException, - InjectionReflectionException, NoSpecifiedConverterException { + public void testFlag() throws Exception { class Options { @Flag boolean a; @@ -110,8 +141,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 +179,9 @@ public static class PP { @Option("q") @MonotonicNonNull String required; + + @OptionalVarargs(prefix = "var_") + Map varargs; } @NullMarked From 03496d574a9318f457e6d0af0ab366253d7b7673 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 19 Jun 2023 15:20:50 +0200 Subject: [PATCH 30/77] improved script error reporting --- .../de/uka/ilkd/key/scripts/RuleCommand.java | 2 +- .../de/uka/ilkd/key/scripts/SetCommand.java | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) 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 7f2ccabee0e..a09032a0891 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 @@ -391,7 +391,7 @@ private List filterList(Services services, Parameters p, ImmutableLis return matchingApps; } - @Documentation("Command that applies a calculus rule.") + @Documentation("This command can be used to apply a calculus rule to the currently active open goal.") public static class Parameters { @Argument @Documentation("Name of the rule to be applied.") 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 50c9ff965a9..cd472ae4d2c 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 @@ -12,6 +12,7 @@ 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; @@ -75,6 +76,15 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup 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) { @@ -137,23 +147,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; - /** key-value pairs to set */ + @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; + } } From 93a9e12af331323b0ced4c2a8e8f9447e377ad82 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 23 Jun 2023 16:25:09 +0200 Subject: [PATCH 31/77] documentation for script commands --- .../java/de/uka/ilkd/key/scripts/EngineState.java | 4 ++-- .../uka/ilkd/key/scripts/InstantiateCommand.java | 15 +++++++++++---- .../java/de/uka/ilkd/key/scripts/RuleCommand.java | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) 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 bd9899f13a6..c2fe91ce237 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 @@ -366,11 +366,11 @@ public ExprEvaluator getEvaluator() { return exprEvaluator; } - public void putUserData(String key, Object val) { + public void putUserData(String key, @Nullable Object val) { userData.put(key, val); } - public Object getUserData(String key) { + public @Nullable Object getUserData(String key) { return userData.get(key); } } 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..6a4aa215974 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,9 +15,11 @@ 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; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.key_project.logic.Name; import org.key_project.logic.PosInTerm; import org.key_project.logic.Term; @@ -229,26 +231,31 @@ public String getDocumentation() { """; } - /** - * - */ + @Documentation("Instantiate a universally quantified formula (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.\n" + + "'with' must be specified.") public static class Parameters { + @Documentation("The toplevel quantified formula to instantiate. Either this or 'var' must be given.") @Option(value = "formula") @Nullable public JTerm formula; + @Documentation("The name of the bound variable to instantiate. Either this or 'formula' must be given.") @Option(value = "var") @Nullable public String var; + @Documentation("The occurrence number of the quantifier over 'var' in the sequent. Default is 1 (the first).") @Option(value = "occ") public @Nullable int occ = 1; + @Documentation("If given, the rule used for instantiation is the one that hides the instantiated formula.") @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/RuleCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java index a09032a0891..e1455d3688c 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 @@ -413,6 +413,7 @@ public static class Parameters { * 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; From 50a7d88807083383af2c8047325258cb8f5af2a8 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 30 Jun 2023 14:37:37 +0200 Subject: [PATCH 32/77] better error messaging in ScriptLineParser --- .../java/de/uka/ilkd/key/scripts/ScriptLineParser.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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..f9ac398f56a 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,9 @@ 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 +248,10 @@ 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() { From c3caa44d575677dfe701045d3e4dd11a031434fe Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 30 Jun 2023 15:05:07 +0200 Subject: [PATCH 33/77] implement a recall mechanism --- key.core/src/main/antlr4/JmlParser.g4 | 2 +- .../ilkd/key/java/Recoder2KeYConverter.java | 2 +- .../key/java/recoderext/JMLTransformer.java | 2 +- .../ilkd/key/java/recoderext/JmlAssert.java | 22 ++++++++++++++----- .../ilkd/key/java/statement/JmlAssert.java | 16 ++++++++++++-- .../key/java/visitor/CreatingASTVisitor.java | 1 + .../de/uka/ilkd/key/rule/JmlAssertRule.java | 19 ++++++++++++++-- .../TextualJMLAssertStatement.java | 10 +++++++-- .../key/speclang/njml/TextualTranslator.java | 3 ++- 9 files changed, 61 insertions(+), 16 deletions(-) diff --git a/key.core/src/main/antlr4/JmlParser.g4 b/key.core/src/main/antlr4/JmlParser.g4 index 02904089f26..110c26f93ff 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -201,7 +201,7 @@ 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) (assertionProof SEMI_TOPLEVEL? | 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; 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 8b140dcf69c..0ec6eaf4783 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,7 @@ 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(), ja.getAssertionProof(), + return new JmlAssert(ja.getKind(), ja.getOptLabel(), ja.getCondition(), ja.getAssertionProof(), positionInfo(ja)); } 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 2b481de72ec..f4cc1498b79 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,7 @@ 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, stat.getAssertionProof()); + 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 ebbce6b73e7..6d1a96b7f28 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 @@ -23,8 +23,11 @@ public class JmlAssert extends JavaStatement { * The kind of this statement either ASSERT or ASSUME */ private final TextualJMLAssertStatement.Kind kind; - private final KeyAst.@Nullable JMLProofScript assertionProof; + /** + * 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 @@ -33,18 +36,20 @@ 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) { - this(kind, condition, null); + 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) { + KeyAst.@Nullable JMLProofScript assertionProof, String optLabel) { this.kind = kind; this.condition = condition; this.assertionProof = assertionProof; + this.optLabel = optLabel; } /** @@ -57,6 +62,7 @@ public JmlAssert(JmlAssert proto) { this.kind = proto.kind; this.condition = proto.condition; this.assertionProof = proto.assertionProof; + this.optLabel = proto.optLabel; } public TextualJMLAssertStatement.Kind getKind() { @@ -100,4 +106,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 3959fb0079d..2fa3a045781 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 @@ -34,6 +34,12 @@ 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 */ @@ -50,11 +56,12 @@ public class JmlAssert extends JavaStatement { * @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; } @@ -65,13 +72,14 @@ 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.assertionProof, other.getPositionInfo()); + this(other.kind, other.optLabel, other.condition, other.assertionProof, other.getPositionInfo()); } public TextualJMLAssertStatement.Kind getKind() { @@ -196,4 +204,8 @@ public void visit(Visitor v) { result = result.prepend(condition.ctx); return result; } + + 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 fff08c99448..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 @@ -1514,6 +1514,7 @@ 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/rule/JmlAssertRule.java b/key.core/src/main/java/de/uka/ilkd/key/rule/JmlAssertRule.java index f5651c14dac..c601822a332 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,7 +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; @@ -24,6 +28,8 @@ 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.Semisequent; +import org.key_project.prover.sequent.Sequent; import org.key_project.prover.sequent.SequentFormula; import org.key_project.util.collection.ImmutableList; @@ -148,6 +154,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); @@ -158,7 +166,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; } @@ -170,7 +178,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"); @@ -180,6 +188,13 @@ 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/speclang/jml/pretranslation/TextualJMLAssertStatement.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/pretranslation/TextualJMLAssertStatement.java index 79b41ec577b..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 @@ -15,19 +15,21 @@ */ 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); + this(kind, clause, null, null); } public TextualJMLAssertStatement(Kind kind, KeyAst.Expression clause, - KeyAst.@Nullable JMLProofScript assertionProof) { + 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() { @@ -71,6 +73,10 @@ public Kind getKind() { return kind; } + public String getOptLabel() { + return optLabel; + } + public enum Kind { ASSERT("assert"), ASSUME("assume"); 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 1158d544fb2..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 @@ -523,7 +523,8 @@ public Object visitAssert_statement(JmlParser.Assert_statementContext ctx) { TextualJMLAssertStatement b = new TextualJMLAssertStatement(TextualJMLAssertStatement.Kind.ASSERT, new KeyAst.Expression(ctx.expression()), - KeyAst.JMLProofScript.fromContext(ctx.assertionProof())); + KeyAst.JMLProofScript.fromContext(ctx.assertionProof()), + ctx.label == null ? null : ctx.label.getText()); constructs = constructs.append(b); return null; } From bd88fe974972043e52323d2f4ec400aa3157b63f Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 7 Jul 2023 22:39:44 +0200 Subject: [PATCH 34/77] allow let commands without "@" --- .../src/main/java/de/uka/ilkd/key/scripts/LetCommand.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 25c3977ab0a..5e6e9a47225 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 @@ -62,13 +62,11 @@ public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst arg 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"); } From d2e3b3402bf97dd3abca8303c4ba0ad900c5cb7a Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 27 Sep 2025 08:11:37 +0200 Subject: [PATCH 35/77] generalising EqualsModProperty --- key.core/src/main/java/de/uka/ilkd/key/java/SourceElement.java | 2 +- key.core/src/main/java/de/uka/ilkd/key/logic/TermImpl.java | 2 +- .../java/de/uka/ilkd/key/logic/equality/EqualsModProperty.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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..d09226af46d 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,7 @@ 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/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}. From d824b15847e71fcd3982ec474505ce098d5e4def Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 22 Jul 2023 12:44:53 +0200 Subject: [PATCH 36/77] a formula parameter for the expand command --- .../ilkd/key/scripts/ExpandDefCommand.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) 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 index 7eaff45ae7d..2d685013d05 100644 --- 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 @@ -1,23 +1,12 @@ package de.uka.ilkd.key.scripts; -import de.uka.ilkd.key.control.AbstractUserInterfaceControl; -import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.equality.TermLabelsProperty; import de.uka.ilkd.key.scripts.meta.Option; -import de.uka.ilkd.key.pp.LogicPrinter; -import de.uka.ilkd.key.proof.BuiltInRuleAppIndex; 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.proof.RuleAppIndex; -import de.uka.ilkd.key.rule.BuiltInRule; -import de.uka.ilkd.key.rule.FindTaclet; -import de.uka.ilkd.key.rule.IBuiltInRuleApp; -import de.uka.ilkd.key.rule.MatchConditions; -import de.uka.ilkd.key.rule.NoFindTaclet; -import de.uka.ilkd.key.rule.NoPosTacletApp; import de.uka.ilkd.key.rule.PosTacletApp; import de.uka.ilkd.key.rule.TacletApp; -import de.uka.ilkd.key.scripts.meta.Option; import org.jspecify.annotations.Nullable; import org.key_project.logic.PosInTerm; import org.key_project.logic.Term; @@ -77,7 +66,18 @@ private TacletApp makeRuleApp(Parameters p, EngineState state) throws ScriptExce getTacletAppAtAndBelow(FILTER, new PosInOccurrence(succForm, PosInTerm.getTopLevel(), false), proof.getServices())); } - apps = apps.filter(it -> it instanceof PosTacletApp && it.posInOccurrence().subTerm().equals(p.on)); + if (p.on != null) { + apps = apps.filter( + it -> it instanceof PosTacletApp && + ((JTerm)it.posInOccurrence().subTerm()).equalsModProperty(p.on, TermLabelsProperty.TERM_LABELS_PROPERTY)); + } else if (p.formula != null) { + apps = apps.filter( + it -> it instanceof PosTacletApp && + ((JTerm)it.posInOccurrence().sequentFormula().formula()).equalsModProperty(p.formula, TermLabelsProperty.TERM_LABELS_PROPERTY)); + } 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'"); @@ -100,6 +100,9 @@ public static class Parameters { public @Nullable Term on; @Option(value = "occ") public @Nullable Integer occ; + @Option(value = "formula") + public @Nullable JTerm formula; + } private static class ExpansionFilter extends TacletFilter { From 6bdbf782c8c2c7c696190ef35bf5b3f3e2940c42 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 27 Sep 2025 08:31:16 +0200 Subject: [PATCH 37/77] spotlessing after a lengthy cherry-pick session from an old branch --- .../ilkd/key/java/Recoder2KeYConverter.java | 3 +- .../de/uka/ilkd/key/java/SourceElement.java | 3 +- .../key/java/recoderext/JMLTransformer.java | 3 +- .../ilkd/key/java/recoderext/JmlAssert.java | 3 +- .../ilkd/key/java/statement/JmlAssert.java | 3 +- .../ilkd/key/macros/ApplyScriptsMacro.java | 30 ++++++------ .../ilkd/key/macros/ScriptAwarePrepMacro.java | 3 ++ .../java/de/uka/ilkd/key/parser/Location.java | 3 +- .../de/uka/ilkd/key/rule/JmlAssertRule.java | 7 +-- .../uka/ilkd/key/scripts/AbstractCommand.java | 3 +- .../uka/ilkd/key/scripts/AssertCommand.java | 40 ++++++++-------- .../uka/ilkd/key/scripts/BranchesCommand.java | 17 ++++--- .../de/uka/ilkd/key/scripts/CheatCommand.java | 22 ++++++--- .../de/uka/ilkd/key/scripts/CutCommand.java | 4 +- .../scripts/DependencyContractCommand.java | 21 ++++++--- .../ilkd/key/scripts/ExpandDefCommand.java | 46 +++++++++++-------- .../ilkd/key/scripts/InstantiateCommand.java | 2 +- .../de/uka/ilkd/key/scripts/LetCommand.java | 14 +++--- .../key/scripts/OneStepSimplifierCommand.java | 19 +++++--- .../de/uka/ilkd/key/scripts/RuleCommand.java | 3 +- .../ilkd/key/scripts/ScriptLineParser.java | 7 ++- .../de/uka/ilkd/key/scripts/SetCommand.java | 11 +++-- .../ilkd/key/scripts/meta/ValueInjector.java | 4 +- .../key/scripts/meta/ValueInjectorTest.java | 6 +-- .../java/de/uka/ilkd/key/gui/IssueDialog.java | 4 +- .../util/collection/ImmutableList.java | 2 +- .../org/key_project/util/java/IOUtil.java | 7 ++- 27 files changed, 170 insertions(+), 120 deletions(-) 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 0ec6eaf4783..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,8 @@ 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.getOptLabel(), ja.getCondition(), ja.getAssertionProof(), + return new JmlAssert(ja.getKind(), ja.getOptLabel(), ja.getCondition(), + ja.getAssertionProof(), positionInfo(ja)); } 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 d09226af46d..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 f4cc1498b79..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, stat.getAssertionProof(), stat.getOptLabel()); + 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 6d1a96b7f28..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 @@ -40,7 +40,8 @@ public class JmlAssert extends JavaStatement { */ private final @Nullable String optLabel; - public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, String optLabel) { + public JmlAssert(TextualJMLAssertStatement.Kind kind, KeyAst.Expression condition, + String optLabel) { this(kind, condition, null, 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 2fa3a045781..6c436749f0b 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 @@ -79,7 +79,8 @@ public JmlAssert(ExtList children) { } public JmlAssert(JmlAssert other) { - this(other.kind, other.optLabel, other.condition, other.assertionProof, other.getPositionInfo()); + this(other.kind, other.optLabel, other.condition, other.assertionProof, + other.getPositionInfo()); } public TextualJMLAssertStatement.Kind getKind() { 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 index fb529ddb3ae..9e825416b5d 100644 --- 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 @@ -3,8 +3,8 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.macros; -import java.io.IOException; import java.util.*; +import java.util.ArrayList; import java.util.stream.Collectors; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; @@ -44,8 +44,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; - public class ApplyScriptsMacro extends AbstractProofMacro { private static final Logger LOGGER = LoggerFactory.getLogger(ApplyScriptsMacro.class); @@ -86,7 +84,8 @@ private static JmlAssert getJmlAssert(Node node) { target = UpdateApplication.getTarget(target); } final SourceElement activeStatement = JavaTools.getActiveStatement(target.javaBlock()); - if (activeStatement instanceof JmlAssert jmlAssert && jmlAssert.getAssertionProof() != null) { + if (activeStatement instanceof JmlAssert jmlAssert + && jmlAssert.getAssertionProof() != null) { return jmlAssert; } } @@ -96,7 +95,7 @@ private static JmlAssert getJmlAssert(Node node) { private static @Nullable JTerm getUpdate(Goal goal) { RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); Term appliedOn = ruleApp.posInOccurrence().subTerm(); - if(appliedOn.op() instanceof UpdateApplication) { + if (appliedOn.op() instanceof UpdateApplication) { return UpdateApplication.getUpdate((JTerm) appliedOn); } return null; @@ -118,7 +117,8 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, continue; } - listener.taskStarted(new DefaultTaskStartedInfo(TaskStartedInfo.TaskKind.Other, "Running attached script from goal " + goal.node().serialNr(), 0)); + 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, proof.getServices()); @@ -127,17 +127,19 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, renderProof(proofScript, termMap, update, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); LOGGER.debug("---- Script"); - LOGGER.debug(renderedProof.stream().map(ScriptCommandAst::asCommandLine).collect(Collectors.joining("\n"))); + LOGGER.debug(renderedProof.stream().map(ScriptCommandAst::asCommandLine) + .collect(Collectors.joining("\n"))); LOGGER.debug("---- End Script"); pse.execute((AbstractUserInterfaceControl) uic, proof); } - listener.taskStarted(new DefaultTaskStartedInfo(TaskStartedInfo.TaskKind.Other, "Running fallback macro on the remaining goals", 0)); + 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) { + if (fallBackMacro != null) { fallBackMacro.applyTo(uic, proof, ImmutableList.of(goal), posInOcc, listener); } @@ -165,8 +167,8 @@ private Map getTermMap(JmlAssert jmlAssert, Services s return result; } - private static List renderProof(KeyAst.JMLProofScript script, - Map termMap, JTerm update, Services services) { + private static List renderProof(KeyAst.JMLProofScript script, + Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); // Do not fail on open proofs // TODO Migrate into SetCommand @@ -182,7 +184,7 @@ private static List renderProof(KeyAst.JMLProofScript script, } private static List renderProofCmd(ProofCmdContext ctx, - Map termMap, JTerm update, Services services) { + Map termMap, JTerm update, Services services) { List result = new ArrayList<>(); // Push the current branch context @@ -198,9 +200,9 @@ private static List renderProofCmd(ProofCmdContext ctx, value = StringUtil.stripQuotes(exp.getText()); } else { value = termMap.get(exp); - if(update != null) { + if (update != null) { // Wrap in update application if an update is present - value = services.getTermBuilder().apply(update, (JTerm)value); + value = services.getTermBuilder().apply(update, (JTerm) value); } } if (argContext.argLabel != null) { 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 index f9d5c8dceac..175f5fa8e51 100644 --- 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 @@ -1,3 +1,6 @@ +/* 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 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 e0f3d95c891..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 @@ -6,7 +6,6 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.util.Comparator; import java.util.Objects; import java.util.Optional; @@ -72,7 +71,7 @@ public static Location fromToken(Token token) { public static Location fromPositionInfo(PositionInfo info) { Optional uri = info.getURI(); - if(uri.isEmpty()) { + if (uri.isEmpty()) { return UNDEFINED; } else { Position pos = info.getStartPosition(); 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 c601822a332..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 @@ -28,7 +28,6 @@ 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.Semisequent; import org.key_project.prover.sequent.Sequent; import org.key_project.prover.sequent.SequentFormula; import org.key_project.util.collection.ImmutableList; @@ -190,8 +189,10 @@ private void setUpUsageGoal(Goal goal, String label, PosInOccurrence occurrence, 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())); + 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); } 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 84648d2198e..55fe6f0777e 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 @@ -48,7 +48,8 @@ public abstract class AbstractCommand implements ProofScriptCommand { } /** - * The POJO class of the parameter object, or null if this command does not take any parameters via + * 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; 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 index a800bddd913..8e5d9ba5e40 100644 --- 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,31 +1,35 @@ -///* This file is part of KeY - https://key-project.org +/* 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 - 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; +// 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 +// * 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 { +// public class AssertCommand extends CutCommand { // -// @Override -// public String getName() { -// return "assert"; -// } +// @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"); -// } -//} +// @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/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java index 33500c34591..23696ecb000 100644 --- 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 @@ -11,20 +11,18 @@ import java.util.Optional; import java.util.Stack; -import de.uka.ilkd.key.logic.op.SortDependingFunction; 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.InjectionException; import de.uka.ilkd.key.scripts.meta.Option; -import de.uka.ilkd.key.scripts.meta.ValueInjector; -import org.jspecify.annotations.Nullable; 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() { @@ -46,7 +44,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup state.putUserData("_branchStack", stack); } - if(args.mode == null) { + if (args.mode == null) { throw new ScriptException("For 'branches', a mode must be specified"); } @@ -77,8 +75,8 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup int no = 0; int found = -1; for (TacletGoalTemplate template : templates) { - if(!"main".equals(template.tag())) { - if(found != -1) { + if (!"main".equals(template.tag())) { + if (found != -1) { throw new ScriptException("More than one non-main goal found"); } found = no; @@ -95,12 +93,13 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup state.setGoal(goal); break; default: - throw new ScriptException("Unknown mode " + args.mode + " for the 'branches' command" ); + throw new ScriptException( + "Unknown mode " + args.mode + " for the 'branches' command"); } } private void ensureSingleGoal() { - //state. + // state. } private Goal findGoalByName(Node root, String branch) throws ScriptException { 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 index c1f5222666d..8805210e2c9 100644 --- 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 @@ -1,3 +1,6 @@ +/* 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; @@ -6,6 +9,7 @@ import de.uka.ilkd.key.rule.NoPosTacletApp; import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; + import org.key_project.logic.ChoiceExpr; import org.key_project.logic.Name; import org.key_project.prover.rules.ApplicationRestriction; @@ -19,10 +23,14 @@ public class CheatCommand extends NoArgumentCommand { private static final Taclet CHEAT_TACLET; static { - TacletApplPart applPart = new TacletApplPart(JavaDLSequentKit.getInstance().getEmptySequent(), - ApplicationRestriction.NONE, 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()); + TacletApplPart applPart = + new TacletApplPart(JavaDLSequentKit.getInstance().getEmptySequent(), + ApplicationRestriction.NONE, 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 @@ -33,13 +41,13 @@ public String getName() { @Override public String getDocumentation() { return "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."; + "be used for testing and proof debugging purposes. It is similar to 'sorry' " + + "in Isabelle or 'admit' in Rocq."; } @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst ast, - EngineState state) + 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 e1809d37167..465c90e41a9 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,6 +3,8 @@ * 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; @@ -14,8 +16,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import java.util.List; - /** * The command object CutCommand has as scriptcommand name "cut" As parameters: a formula with the * id "#2" 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 index 81e755d770e..99c1deaaf5a 100644 --- 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 @@ -1,12 +1,18 @@ +/* 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.scripts.meta.Option; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.rule.IBuiltInRuleApp; import de.uka.ilkd.key.rule.UseDependencyContractApp; -import org.jspecify.annotations.Nullable; +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; @@ -15,8 +21,7 @@ import org.key_project.util.collection.ImmutableArray; import org.key_project.util.collection.ImmutableList; -import java.util.ArrayList; -import java.util.List; +import org.jspecify.annotations.Nullable; public class DependencyContractCommand extends AbstractCommand { @@ -38,12 +43,13 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte if (arguments.heap == null) { Services services = goal.proof().getServices(); - arguments.heap = services.getTermFactory().createTerm(services.getTypeConverter().getHeapLDT().getHeap()); + arguments.heap = services.getTermFactory() + .createTerm(services.getTypeConverter().getHeapLDT().getHeap()); } List pios = find(arguments.on, goal.sequent()); - if(pios.isEmpty()) { + if (pios.isEmpty()) { throw new ScriptException("dependency contract not applicable."); } else if (pios.size() > 1) { throw new ScriptException("no unique application"); @@ -90,7 +96,8 @@ private void apply(Goal goal, UseDependencyContractApp ruleApp, Parameters argum 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()); + 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); 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 index 2d685013d05..113d4b56650 100644 --- 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 @@ -1,13 +1,16 @@ +/* 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.logic.JTerm; import de.uka.ilkd.key.logic.equality.TermLabelsProperty; -import de.uka.ilkd.key.scripts.meta.Option; 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 org.jspecify.annotations.Nullable; +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.proof.rulefilter.TacletFilter; @@ -16,6 +19,8 @@ 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(); @@ -35,8 +40,9 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte Goal g = state().getFirstOpenAutomaticGoal(); TacletApp theApp = makeRuleApp(args, state()); - ImmutableList completions = theApp.findIfFormulaInstantiations(g.sequent(), g.proof().getServices()); - if(completions == null || completions.isEmpty()) { + ImmutableList completions = + theApp.findIfFormulaInstantiations(g.sequent(), g.proof().getServices()); + if (completions == null || completions.isEmpty()) { throw new ScriptException("Cannot complete the rule app"); } @@ -57,37 +63,41 @@ private TacletApp makeRuleApp(Parameters p, EngineState state) throws ScriptExce 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())); + 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())); + 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 && - ((JTerm)it.posInOccurrence().subTerm()).equalsModProperty(p.on, TermLabelsProperty.TERM_LABELS_PROPERTY)); + it -> it instanceof PosTacletApp && + ((JTerm) it.posInOccurrence().subTerm()).equalsModProperty(p.on, + TermLabelsProperty.TERM_LABELS_PROPERTY)); } else if (p.formula != null) { apps = apps.filter( - it -> it instanceof PosTacletApp && - ((JTerm)it.posInOccurrence().sequentFormula().formula()).equalsModProperty(p.formula, TermLabelsProperty.TERM_LABELS_PROPERTY)); + it -> it instanceof PosTacletApp && + ((JTerm) it.posInOccurrence().sequentFormula().formula()).equalsModProperty( + p.formula, TermLabelsProperty.TERM_LABELS_PROPERTY)); } else { throw new ScriptException("Either 'formula' or 'on' must be specified"); } - if(apps.isEmpty()) { + 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."); + } 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) { + if (apps.size() != 1) { throw new ScriptException("The 'on' parameter is not unique"); } return apps.head(); @@ -110,7 +120,7 @@ private static class ExpansionFilter extends TacletFilter { @Override protected boolean filter(Taclet taclet) { String name = taclet.name().toString(); - return name.startsWith("Class_invariant_axiom_for") || + 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/InstantiateCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/InstantiateCommand.java index 6a4aa215974..6dd0c2e7f96 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 @@ -19,7 +19,6 @@ import de.uka.ilkd.key.scripts.meta.Flag; import de.uka.ilkd.key.scripts.meta.Option; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.key_project.logic.Name; import org.key_project.logic.PosInTerm; import org.key_project.logic.Term; @@ -32,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; 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 5e6e9a47225..b061d0aba42 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 @@ -34,13 +34,13 @@ /// * Jan,2025 (weigl): add new parameter {@code force} to override bindings. @NullMarked @Documentation(""" - 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.""") + 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 class LetCommand implements ProofScriptCommand { @Override 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 index 184116ca1f2..741b6eeee58 100644 --- 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 @@ -1,15 +1,20 @@ +/* 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.Option; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.rule.IBuiltInRuleApp; import de.uka.ilkd.key.rule.OneStepSimplifierRuleApp; -import org.jspecify.annotations.Nullable; +import de.uka.ilkd.key.scripts.meta.Option; + import org.key_project.logic.PosInTerm; 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 OneStepSimplifierCommand extends AbstractCommand { public OneStepSimplifierCommand() { @@ -27,9 +32,10 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte var arguments = state().getValueInjector().inject(new Parameters(), command); final Goal goal = state.getFirstOpenAutomaticGoal(); - if(arguments.antecedent) { + if (arguments.antecedent) { for (SequentFormula sf : goal.sequent().antecedent()) { - ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, new PosInOccurrence(sf, PosInTerm.getTopLevel(), true)); + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, + new PosInOccurrence(sf, PosInTerm.getTopLevel(), true)); for (IBuiltInRuleApp builtin : builtins) { if (builtin instanceof OneStepSimplifierRuleApp) { goal.apply(builtin); @@ -38,9 +44,10 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte } } - if(arguments.succedent) { + if (arguments.succedent) { for (SequentFormula sf : goal.sequent().succedent()) { - ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, new PosInOccurrence(sf, PosInTerm.getTopLevel(), false)); + ImmutableList builtins = goal.ruleAppIndex().getBuiltInRules(goal, + new PosInOccurrence(sf, PosInTerm.getTopLevel(), false)); for (IBuiltInRuleApp builtin : builtins) { if (builtin instanceof OneStepSimplifierRuleApp) { goal.apply(builtin); 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 e1455d3688c..b96b6609476 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 @@ -365,7 +365,8 @@ private static String formatTermString(String str) { /* * Filter those apps from a list that are according to the parameters. */ - private List filterList(Services services, Parameters p, ImmutableList list) { + private List filterList(Services services, Parameters p, + ImmutableList list) { List matchingApps = new ArrayList<>(); for (TacletApp tacletApp : list) { if (tacletApp instanceof PosTacletApp pta) { 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 f9ac398f56a..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,7 +15,8 @@ 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. + * This class was used to parse script lines before the parsing was integrated into the general + * ANTLR parser for KeY files. * * @author mattias ulbrich */ @@ -248,9 +249,7 @@ private void exc(int c) throws ScriptException { } private Location getLocation() { - Position pos = line >= 1 ? - Position.newOneBased(line, col) : - Position.UNDEFINED; + Position pos = line >= 1 ? Position.newOneBased(line, col) : Position.UNDEFINED; return new Location(fileURI, pos); } 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 cd472ae4d2c..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 @@ -51,11 +51,11 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup : StrategyProperties.OSS_OFF); Strategy.updateStrategySettings(proof, newProps); OneStepSimplifier.refreshOSS(proof); - } + } if (args.proofSteps != null) { state.setMaxAutomaticSteps(args.proofSteps); - } + } if (args.stackAction != null) { Stack stack = @@ -76,10 +76,11 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup default: throw new IllegalArgumentException("stack must be either push or pop."); } - } else if(args.userKey != null) { + } 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\"."); + 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 { 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 4f4d93eee73..a875ce6c731 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 @@ -158,10 +158,10 @@ public T inject(T obj, ScriptCommandAst arguments) unhandledPos.get(), count, obj.getClass().getName())); } - if(obj instanceof VerifyableParameters vp) { + if (obj instanceof VerifyableParameters vp) { try { vp.verifyParameters(); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { throw new InjectionException(e); } } 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 3ddbf9239c4..991dc9c3af4 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 @@ -64,12 +64,12 @@ public void testUnknownArguments() { PP pp = new PP(); Map args = new HashMap<>(); ScriptCommandAst ast = new ScriptCommandAst("pp", args, new LinkedList<>(), - null); + null); args.put("i", "42"); args.put("b", "true"); args.put("unknownParameter", "unknownValue"); assertThrows(UnknownArgumentException.class, - () -> ValueInjector.injection(new PPCommand(), pp, ast)); + () -> ValueInjector.injection(new PPCommand(), pp, ast)); } // copied from old jmlScript branch ... possibly needs adaptation @@ -78,7 +78,7 @@ public void testVarargsOld() throws Exception { PP pp = new PP(); Map args = new HashMap<>(); ScriptCommandAst ast = new ScriptCommandAst("pp", args, new LinkedList<>(), - null); + null); args.put("#literal", "here goes the entire string..."); args.put("i", "42"); args.put("b", "true"); 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 032b47ca351..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,8 +736,8 @@ 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 ++; + while (offset < source.length() && Character.isWhitespace(source.charAt(offset))) { + offset++; } int end = offset; while (end < source.length() && !Character.isWhitespace(source.charAt(end))) { 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 17d19e63f0b..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 @@ -370,7 +370,7 @@ default T last() { * ({@code index < 0 || index >= size()}) */ default T get(int index) { - if(index < 0 || index >= size()) { + 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/java/IOUtil.java b/key.util/src/main/java/org/key_project/util/java/IOUtil.java index 72b1b9d5c4b..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 @@ -702,7 +702,8 @@ 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)); + return new URL("memory", "", 0, String.format("/%x", System.identityHashCode(data)), + new MemoryDataHandler(data)); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -710,13 +711,15 @@ public static URL makeMemoryURL(String data) { 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")) { + if (!u.getProtocol().equals("memory")) { throw new IOException("Unsupported protocol"); } return new URLConnection(u) { From cde4321eab387288b657ef37318f710870bce285 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 1 Oct 2025 18:14:55 +0200 Subject: [PATCH 38/77] towards 'obtain' in JML scripts --- key.core/src/main/antlr4/JmlLexer.g4 | 1 + key.core/src/main/antlr4/JmlParser.g4 | 17 ++- .../java/de/uka/ilkd/key/java/JavaInfo.java | 3 +- .../ilkd/key/java/statement/JmlAssert.java | 15 +- .../ilkd/key/macros/ApplyScriptsMacro.java | 139 ++++++++++++++---- .../java/de/uka/ilkd/key/nparser/KeyAst.java | 80 ++++++---- .../proof/mgt/SpecificationRepository.java | 7 +- .../jml/translation/JMLSpecFactory.java | 4 +- .../ProgramVariableCollection.java | 2 +- .../de/uka/ilkd/key/speclang/njml/JmlIO.java | 20 ++- .../uka/ilkd/key/scripts/JmlScriptTest.java | 70 +++++++++ .../de/uka/ilkd/key/scripts/jml/Obtain1.java | 11 ++ .../de/uka/ilkd/key/scripts/jml/project.key | 14 ++ 13 files changed, 310 insertions(+), 73 deletions(-) create mode 100644 key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/Obtain1.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/project.key diff --git a/key.core/src/main/antlr4/JmlLexer.g4 b/key.core/src/main/antlr4/JmlLexer.g4 index 381dda8a5d2..8b2d76b6bad 100644 --- a/key.core/src/main/antlr4/JmlLexer.g4 +++ b/key.core/src/main/antlr4/JmlLexer.g4 @@ -246,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 110c26f93ff..cfbaeb4fee8 100644 --- a/key.core/src/main/antlr4/JmlParser.g4 +++ b/key.core/src/main/antlr4/JmlParser.g4 @@ -9,6 +9,9 @@ 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; @@ -210,9 +213,19 @@ assert_statement: (ASSERT (label=IDENT COLON)? expression | UNREACHABLE) (assert // --- proof scripts in JML assertionProof: BY (proofCmd | LBRACE ( proofCmd )+ RBRACE) ; proofCmd: - cmd=IDENT ( proofArg )* - ( SEMI | BY ( proofCmd | LBRACE (proofCmd+ | proofCmdCase+) RBRACE )) + // 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 )* 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..0bbeeb58f51 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 @@ -18,6 +18,7 @@ import de.uka.ilkd.key.speclang.SpecificationElement; import de.uka.ilkd.key.util.Debug; +import org.jspecify.annotations.Nullable; import org.key_project.logic.Name; import org.key_project.logic.Namespace; import org.key_project.logic.sort.Sort; @@ -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/statement/JmlAssert.java b/key.core/src/main/java/de/uka/ilkd/key/java/statement/JmlAssert.java index 6c436749f0b..1d594566a5e 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,13 +8,15 @@ 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.antlr.v4.runtime.ParserRuleContext; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -197,8 +199,8 @@ public void visit(Visitor v) { * * @return a freshly created list of at least one term */ - public @NonNull ImmutableList collectTerms() { - ImmutableList result = ImmutableList.of(); + public @NonNull ImmutableList collectTerms() { + ImmutableList result = ImmutableList.of(); if (assertionProof != null) { result = result.prepend(assertionProof.collectTerms()); } @@ -206,6 +208,13 @@ public void visit(Visitor v) { 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/macros/ApplyScriptsMacro.java b/key.core/src/main/java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java index 9e825416b5d..7b3f9739c22 100644 --- 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 @@ -13,18 +13,23 @@ 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.logic.DefaultVisitor; import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.op.LocationVariable; import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Node; +import de.uka.ilkd.key.proof.ProgVarReplacer; import de.uka.ilkd.key.proof.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.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; @@ -76,6 +81,27 @@ public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, || goals.exists(g -> getJmlAssert(g.node()) != null); } + record ObtainAwareTerm(JTerm term) { + JTerm resolve(Map obtainMap, Services services) { + ProgVarReplacer pvr = new ProgVarReplacer(obtainMap, services); + 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) { @@ -122,10 +148,15 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); Map termMap = getTermMap(jmlAssert, proof.getServices()); + // We heavily rely on that variables have been computed before, otherwise this will raise an NPE. + Map obtainMap = makeObtainVarMap(jmlAssert.collectVariablesInProof(null)); JTerm update = getUpdate(goal); List renderedProof = renderProof(proofScript, termMap, update, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); + pse.getStateMap().putUserData("jml.obtainVarMap", obtainMap); + pse.getStateMap().getValueInjector().addConverter(JTerm.class, ObtainAwareTerm.class, + oat -> oat.resolve(obtainMap, goal.proof().getServices())); LOGGER.debug("---- Script"); LOGGER.debug(renderedProof.stream().map(ScriptCommandAst::asCommandLine) .collect(Collectors.joining("\n"))); @@ -157,18 +188,25 @@ private Map getTermMap(JmlAssert jmlAssert, Services s "No specification found for JML assert statement at " + jmlAssert); } ImmutableList terms = jmlspec.terms().tail(); - ImmutableList jmlExprs = jmlAssert.collectTerms().tail(); + ImmutableList jmlExprs = jmlAssert.collectTerms().tail(); Map result = new IdentityHashMap<>(); assert terms.size() == jmlExprs.size(); for (int i = 0; i < terms.size(); i++) { - // TODO build a map from jmlExprs.get(i) to terms.get(i) result.put(jmlExprs.get(i), terms.get(i)); } return result; } + 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, JTerm update, Services services) { + Map termMap, JTerm update, Services services) throws ScriptException { List result = new ArrayList<>(); // Do not fail on open proofs // TODO Migrate into SetCommand @@ -184,13 +222,79 @@ private static List renderProof(KeyAst.JMLProofScript script, } private static List renderProofCmd(ProofCmdContext ctx, - Map termMap, JTerm update, Services services) { + Map termMap, JTerm 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, JTerm 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()); + }; + + 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 = services.getTermBuilder().apply(update, (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, JTerm update, Services services) { Map named = new HashMap<>(); List positional = new ArrayList<>(); for (ProofArgContext argContext : ctx.proofArg()) { @@ -211,31 +315,8 @@ private static List renderProofCmd(ProofCmdContext ctx, positional.add(value); } } - result.add(new ScriptCommandAst(ctx.cmd.getText(), named, positional, - Location.fromToken(ctx.start))); - - // handle proofCmd if present - if (!ctx.proofCmd().isEmpty()) { - result.add(new ScriptCommandAst("branches", Map.of(), List.of("single"))); - for (ProofCmdContext proofCmdContext : ctx.proofCmd()) { - result.addAll(renderProofCmd(proofCmdContext, termMap, update, services)); - } - } - - // handle proofCmdCase if present - for (ProofCmdCaseContext pcase : ctx.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; + return new ScriptCommandAst(ctx.cmd.getText(), named, positional, + Location.fromToken(ctx.start)); } private static boolean isStringLiteral(JmlParser.ExpressionContext ctx) { 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 722ddbf9e55..1713e48281c 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,10 @@ 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; @@ -225,6 +230,40 @@ public Expression(JmlParser.@NonNull ExpressionContext ctx) { } public static class JMLProofScript extends KeyAst { + + private static class ObtainedVarsVisitor extends JmlParserBaseVisitor { + /// To make debugging easier, obtained variables have a special (hopefully but not necessarily unique) prefix. + public static final String OBTAIN_PREFIX = "_obtained_"; + 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(OBTAIN_PREFIX + 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); } @@ -237,32 +276,13 @@ public static JMLProofScript fromContext(JmlParser.AssertionProofContext ctx) { } } - /** - * Collect all JML expressions in a script (and potentially sub-blocks) - * - * @param cmd the command to collect from - * @return a list in reverse(!) order of all expressions in cmd - */ - private static ImmutableList collectTerms( - JmlParser.ProofCmdContext cmd) { - ImmutableList result = ImmutableList.of(); - for (JmlParser.ProofArgContext arg : cmd.proofArg()) { - JmlParser.ExpressionContext exp = arg.expression(); - if (exp != null) { - result = result.prepend(exp); - } - } - for (JmlParser.ProofCmdContext childCmd : cmd.proofCmd()) { - result = result.prepend(collectTerms(childCmd)); - } - if (cmd.proofCmdCase() != null) { - for (JmlParser.ProofCmdCaseContext pcase : cmd.proofCmdCase()) { - for (JmlParser.ProofCmdContext childCmd : pcase.proofCmd()) { - result = result.prepend(collectTerms(childCmd)); - } - } + public ImmutableList getObtainedProgramVars(JmlIO io) { + if(obtainedProgramVars == null) { + var visitor = new ObtainedVarsVisitor(io); + ctx.accept(visitor); + obtainedProgramVars = visitor.collectedVars; } - return result; + return obtainedProgramVars; } /** @@ -270,12 +290,10 @@ private static ImmutableList collectTerms( * * Todo: Consider caching the result if this is called very often. */ - public @NonNull ImmutableList collectTerms() { - ImmutableList result = ImmutableList.of(); - for (JmlParser.ProofCmdContext cmd : ctx.proofCmd()) { - result = result.prepend(collectTerms(cmd)); - } - return result.reverse(); + public @NonNull ImmutableList collectTerms() { + TermCollectionVisitor visitor = new TermCollectionVisitor(); + ctx.accept(visitor); + return visitor.collectedTerms.reverse(); } } 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..b42d6225fca 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 @@ -1879,10 +1879,11 @@ 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 +1910,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/speclang/jml/translation/JMLSpecFactory.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/jml/translation/JMLSpecFactory.java index 50312133e9b..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,6 +1513,8 @@ public void translateJmlAssertCondition(final JmlAssert jmlAssert, final IProgra .exceptionVariable(pv.excVar) .atPres(pv.atPres) .atBefore(pv.atBefores); + ImmutableList varsInProof = jmlAssert.collectVariablesInProof(io); + io.parameters(pv.paramVars.prepend(varsInProof)); ImmutableList terms = jmlAssert.collectTerms().map(io::translateTerm); services.getSpecificationRepository().addStatementSpec( jmlAssert, 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/JmlIO.java b/key.core/src/main/java/de/uka/ilkd/key/speclang/njml/JmlIO.java index 7fce7c039aa..71c5b5f9097 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/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..bb5db7edbb6 --- /dev/null +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java @@ -0,0 +1,70 @@ +package de.uka.ilkd.key.scripts; + +import de.uka.ilkd.key.control.DefaultUserInterfaceControl; +import de.uka.ilkd.key.control.KeYEnvironment; +import de.uka.ilkd.key.control.UserInterfaceControl; +import de.uka.ilkd.key.nparser.KeyAst; +import de.uka.ilkd.key.proof.io.ProblemLoaderControl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +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.Collections; +import java.util.Comparator; +import java.util.stream.Stream; + +public class JmlScriptTest { + + private static final Path KEY_FILE; + 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 = "{0}") + @MethodSource("filesProvider") + public void testJmlScript(Path path) throws Exception { + + 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); + KeyAst.ProofScript script = env.getProofScript(); + if (script != null) { + ProofScriptEngine pse = new ProofScriptEngine(script); + pse.execute(env.getUi(), env.getLoadedProof()); + } + // TODO read comments from java file to allow for more than only closed proofs ... + Assertions.assertTrue(env.getLoadedProof().closed(), "Proof did not close."); + } finally { + // Uncomment the following line to delete the temporary directory after the test + // Files.walk(tmpDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + + } + + public static Stream filesProvider() throws URISyntaxException, IOException { + URL jmlUrl = JmlScriptTest.class.getResource("jml"); + return Files.list(Paths.get(jmlUrl.toURI())) + .filter(p -> p.toString().endsWith(".java")) + .map(p -> Arguments.of(p)); + } + + + +} 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..34814fd588d --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/Obtain1.java @@ -0,0 +1,11 @@ +class Test { + //@ ensures true; + void test() { + int x = 42; + /*@ assert x == 42 \by { + obtain int y = 41; + assert x+1 == 42; + } */ + } + +} \ No newline at end of file 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"; +} From bd35ba0d513672f89b1ae8f1ba41a662e36e32fe Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 1 Oct 2025 18:40:45 +0200 Subject: [PATCH 39/77] update ProofScriptEngine's workflow after discussion with Alexander --- .../ilkd/key/macros/ApplyScriptsMacro.java | 5 +- .../de/uka/ilkd/key/scripts/EngineState.java | 2 +- .../ilkd/key/scripts/ProofScriptEngine.java | 107 ++++++------------ .../uka/ilkd/key/scripts/ScriptCommand.java | 4 +- .../uka/ilkd/key/logic/TestLocalSymbols.java | 4 +- .../key/proof/proverules/ProveRulesTest.java | 4 +- .../key/proof/runallproofs/ProveTest.java | 4 +- .../proofcollection/TestFile.java | 4 +- .../ilkd/key/scripts/FocusCommandTest.java | 8 +- .../uka/ilkd/key/scripts/JmlScriptTest.java | 4 +- .../key/scripts/TestProofScriptCommand.java | 7 +- .../ilkd/key/scripts/meta/RewriteTest.java | 8 +- .../uka/ilkd/key/gui/ProofScriptWorker.java | 6 +- .../key/ui/ConsoleUserInterfaceControl.java | 4 +- 14 files changed, 70 insertions(+), 101 deletions(-) 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 index 7b3f9739c22..6557a92ea0f 100644 --- 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 @@ -153,7 +153,8 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, JTerm update = getUpdate(goal); List renderedProof = renderProof(proofScript, termMap, update, proof.getServices()); - ProofScriptEngine pse = new ProofScriptEngine(renderedProof, goal); + 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())); @@ -162,7 +163,7 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, .collect(Collectors.joining("\n"))); LOGGER.debug("---- End Script"); - pse.execute((AbstractUserInterfaceControl) uic, proof); + pse.execute((AbstractUserInterfaceControl) uic, renderedProof); } listener.taskStarted(new DefaultTaskStartedInfo(TaskStartedInfo.TaskKind.Other, "Running fallback macro on the remaining goals", 0)); 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 c2fe91ce237..c20b6e8ab8e 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 @@ -146,7 +146,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); } 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 229fa298489..6215cc9e518 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,6 +13,7 @@ import java.util.stream.Collectors; import de.uka.ilkd.key.control.AbstractUserInterfaceControl; +import de.uka.ilkd.key.control.DefaultUserInterfaceControl; import de.uka.ilkd.key.nparser.KeYParser; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.nparser.ParsingFacade; @@ -31,44 +32,21 @@ * @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) throws IOException { + super(); + this.stateMap = new EngineState(proof, this); } private static Map loadCommands() { @@ -84,49 +62,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,8 +115,8 @@ 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/ScriptCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommand.java index 66d1ec2b656..50a6eff3b2c 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 @@ -38,9 +38,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/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/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/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 index bb5db7edbb6..103619375f5 100644 --- 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 @@ -46,8 +46,8 @@ public void testJmlScript(Path path) throws Exception { KeYEnvironment env = KeYEnvironment.load(projectFile); KeyAst.ProofScript script = env.getProofScript(); if (script != null) { - ProofScriptEngine pse = new ProofScriptEngine(script); - pse.execute(env.getUi(), env.getLoadedProof()); + ProofScriptEngine pse = new ProofScriptEngine(env.getLoadedProof()); + pse.execute(env.getUi(), script); } // TODO read comments from java file to allow for more than only closed proofs ... Assertions.assertTrue(env.getLoadedProof().closed(), "Proof did not close."); 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..b4724c50264 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 @@ -13,6 +13,7 @@ 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; @@ -77,12 +78,12 @@ void testProofScript(TestInstance data) throws Exception { 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) { assertTrue(data.exception != null && !data.exception.isEmpty(), "An exception was not expected, but got " + ex.getMessage()); 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.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..14cc9adcda5 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 @@ -5,6 +5,7 @@ import java.awt.*; import java.awt.Dialog.ModalityType; +import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.CancellationException; @@ -75,14 +76,15 @@ public ProofScriptWorker(KeYMediator mediator, KeyAst.ProofScript script, this.mediator = mediator; this.script = script; this.initiallySelectedGoal = initiallySelectedGoal; - engine = new ProofScriptEngine(script, initiallySelectedGoal); + engine = new ProofScriptEngine(initiallySelectedGoal.proof()); + engine.setInitiallySelectedGoal(initiallySelectedGoal); } @Override protected @Nullable Object doInBackground() throws Exception { try { 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); } 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)); From 31f73cb2500e1d853237f0be03bc6cc1a5805bb0 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 1 Oct 2025 21:11:09 +0200 Subject: [PATCH 40/77] consolidating, introducing test cases for jml scripts --- .../ilkd/key/macros/ApplyScriptsMacro.java | 3 -- .../java/de/uka/ilkd/key/nparser/KeyAst.java | 4 +- .../de/uka/ilkd/key/scripts/CheatCommand.java | 4 +- .../ilkd/key/scripts/ProofScriptEngine.java | 2 +- ...de.uka.ilkd.key.scripts.ProofScriptCommand | 3 +- .../uka/ilkd/key/scripts/JmlScriptTest.java | 50 ++++++++++++++++--- .../ilkd/key/scripts/jml/NestedAssert.java | 11 ++++ .../de/uka/ilkd/key/scripts/jml/Obtain1.java | 9 ++-- 8 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/NestedAssert.java 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 index 6557a92ea0f..1c7cfac878c 100644 --- 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 @@ -209,9 +209,6 @@ private Map makeObtainVarMap(ImmutableList renderProof(KeyAst.JMLProofScript script, Map termMap, JTerm update, Services services) throws ScriptException { List result = new ArrayList<>(); - // Do not fail on open proofs - // TODO Migrate into SetCommand - result.add(new ScriptCommandAst("failonopen", Map.of(), List.of("off"))); // Push current settings onto the settings stack result.add(new ScriptCommandAst("set", Map.of("stack", "push"), List.of())); for (ProofCmdContext proofCmdContext : script.ctx.proofCmd()) { 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 1713e48281c..95d0cbf698f 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 @@ -232,8 +232,6 @@ public Expression(JmlParser.@NonNull ExpressionContext ctx) { public static class JMLProofScript extends KeyAst { private static class ObtainedVarsVisitor extends JmlParserBaseVisitor { - /// To make debugging easier, obtained variables have a special (hopefully but not necessarily unique) prefix. - public static final String OBTAIN_PREFIX = "_obtained_"; private ImmutableList collectedVars = ImmutableList.of(); private final JmlIO io; @@ -245,7 +243,7 @@ private ObtainedVarsVisitor(JmlIO io) { public Void visitProofCmd(JmlParser.ProofCmdContext ctx) { if(ctx.obtain != null) { KeYJavaType type = io.translateType(ctx.typespec()); - ProgramElementName name = new ProgramElementName(OBTAIN_PREFIX + ctx.var.getText()); + ProgramElementName name = new ProgramElementName(ctx.var.getText()); collectedVars = collectedVars.prepend(new LocationVariable(name, type, true)); } return null; 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 index 8805210e2c9..65b697d1219 100644 --- 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 @@ -25,8 +25,8 @@ public class CheatCommand extends NoArgumentCommand { static { TacletApplPart applPart = new TacletApplPart(JavaDLSequentKit.getInstance().getEmptySequent(), - ApplicationRestriction.NONE, ImmutableList.of(), ImmutableList.of(), - ImmutableList.of(), ImmutableList.of()); + 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, 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 6215cc9e518..4259296d086 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 @@ -44,7 +44,7 @@ public class ProofScriptEngine { */ private final EngineState stateMap; - public ProofScriptEngine(Proof proof) throws IOException { + public ProofScriptEngine(Proof proof) { super(); this.stateMap = new EngineState(proof, this); } 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 82b1c68cd8e..66425d87b72 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 @@ -37,4 +37,5 @@ 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 \ No newline at end of file +de.uka.ilkd.key.scripts.BranchesCommand +de.uka.ilkd.key.scripts.CheatCommand \ No newline at end of file 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 index 103619375f5..0e93d9bb60e 100644 --- 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 @@ -1,5 +1,7 @@ package de.uka.ilkd.key.scripts; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import de.uka.ilkd.key.control.DefaultUserInterfaceControl; import de.uka.ilkd.key.control.KeYEnvironment; import de.uka.ilkd.key.control.UserInterfaceControl; @@ -10,6 +12,9 @@ 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 recoder.util.Debug; import java.io.File; import java.io.IOException; @@ -18,13 +23,16 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import java.util.Comparator; +import java.util.logging.LogManager; +import java.util.stream.Collectors; import java.util.stream.Stream; public class JmlScriptTest { private static final Path KEY_FILE; + private static final Logger LOGGER = LoggerFactory.getLogger(JmlScriptTest.class); + static { URL url = JmlScriptTest.class.getResource("jml/project.key"); try { @@ -34,9 +42,11 @@ public class JmlScriptTest { } } - @ParameterizedTest(name = "{0}") + @ParameterizedTest(name = "{1}") @MethodSource("filesProvider") - public void testJmlScript(Path path) throws Exception { + public void testJmlScript(Path path, String identifier) throws Exception { + + Parameters params = readParams(path); Path tmpDir = Files.createTempDirectory("key.jmltest."); try { @@ -49,22 +59,48 @@ public void testJmlScript(Path path) throws Exception { ProofScriptEngine pse = new ProofScriptEngine(env.getLoadedProof()); pse.execute(env.getUi(), script); } - // TODO read comments from java file to allow for more than only closed proofs ... - Assertions.assertTrue(env.getLoadedProof().closed(), "Proof did not close."); + + 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 - // Files.walk(tmpDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + if(params.deleteTmpDir) { + 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).trim()) + .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"); return Files.list(Paths.get(jmlUrl.toURI())) .filter(p -> p.toString().endsWith(".java")) - .map(p -> Arguments.of(p)); + .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; + } } 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 index 34814fd588d..aeb6f6b356d 100644 --- 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 @@ -1,11 +1,14 @@ +//! deleteTmpDir : false + class Test { //@ ensures true; void test() { int x = 42; /*@ assert x == 42 \by { obtain int y = 41; - assert x+1 == 42; - } */ + assert y+1 == 42; + auto; + } */ } -} \ No newline at end of file +} From 6b49d282bd210b1e51e205da59f21531f6cb6cd5 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 1 Oct 2025 23:51:36 +0200 Subject: [PATCH 41/77] first working obtain scripts --- .../ilkd/key/macros/ApplyScriptsMacro.java | 26 +-- .../uka/ilkd/key/scripts/ObtainCommand.java | 188 ++++++++++++++++++ .../ilkd/key/scripts/meta/ValueInjector.java | 2 +- ...de.uka.ilkd.key.scripts.ProofScriptCommand | 1 + .../ilkd/key/proof/rules/firstOrderRules.key | 5 + .../uka/ilkd/key/scripts/JmlScriptTest.java | 13 +- .../de/uka/ilkd/key/scripts/jml/Obtain1.java | 3 +- .../ilkd/key/scripts/jml/ObtainFromGoal.java | 18 ++ 8 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/ObtainCommand.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainFromGoal.java 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 index 1c7cfac878c..9bd55b96f3e 100644 --- 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 @@ -15,14 +15,12 @@ import de.uka.ilkd.key.java.statement.JmlAssert; import de.uka.ilkd.key.logic.DefaultVisitor; import de.uka.ilkd.key.logic.JTerm; +import de.uka.ilkd.key.logic.op.JFunction; import de.uka.ilkd.key.logic.op.LocationVariable; import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.parser.Location; -import de.uka.ilkd.key.proof.Goal; -import de.uka.ilkd.key.proof.Node; -import de.uka.ilkd.key.proof.ProgVarReplacer; -import de.uka.ilkd.key.proof.Proof; +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; @@ -36,6 +34,7 @@ import de.uka.ilkd.key.speclang.njml.JmlParser.ProofCmdContext; import org.key_project.logic.Term; +import org.key_project.logic.op.SortedOperator; import org.key_project.prover.engine.ProverTaskListener; import org.key_project.prover.engine.TaskStartedInfo; import org.key_project.prover.rules.RuleApp; @@ -82,14 +81,14 @@ public boolean canApplyTo(Proof proof, ImmutableList<@NonNull Goal> goals, } record ObtainAwareTerm(JTerm term) { - JTerm resolve(Map obtainMap, Services services) { - ProgVarReplacer pvr = new ProgVarReplacer(obtainMap, services); + 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) { + private void assertNoObtainVarsLeft(JTerm term, Map obtainMap) { var v = new DefaultVisitor() { @Override public void visit(Term visited) { @@ -149,7 +148,7 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); Map termMap = getTermMap(jmlAssert, proof.getServices()); // We heavily rely on that variables have been computed before, otherwise this will raise an NPE. - Map obtainMap = makeObtainVarMap(jmlAssert.collectVariablesInProof(null)); + Map obtainMap = makeObtainVarMap(jmlAssert.collectVariablesInProof(null)); JTerm update = getUpdate(goal); List renderedProof = renderProof(proofScript, termMap, update, proof.getServices()); @@ -198,8 +197,8 @@ private Map getTermMap(JmlAssert jmlAssert, Services s return result; } - private Map makeObtainVarMap(ImmutableList locationVariables) { - HashMap result = new HashMap<>(); + private Map makeObtainVarMap(ImmutableList locationVariables) { + HashMap result = new HashMap<>(); for (LocationVariable lv : locationVariables) { result.put(lv, null); } @@ -272,6 +271,8 @@ private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map throw new ScriptException("Unknown obtain kind: " + ctx.obtKind.getText()); }; + named.put("var", ctx.var.getText()); + if(ctx.expression() == null) { named.put(argName, true); } else { @@ -307,10 +308,11 @@ private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map + *

  • #2 = rule name
  • + *
  • on= key.core.logic.Term on which the rule should be applied to as String (find part of the + * rule)
  • + *
  • formula= toplevel formula in which term appears in
  • + *
  • occ = occurrence number
  • + *
  • inst_= instantiation
  • + * + */ +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("TODO.") + public static class Parameters implements ValueInjector.VerifyableParameters { + @Option(value = "var") + @Documentation("Name of the instantiated variable.") + 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/meta/ValueInjector.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java index a875ce6c731..8300997d66e 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 @@ -48,7 +48,7 @@ private record ConverterKey( Class source, Class target) { } - interface VerifyableParameters { + public interface VerifyableParameters { void verifyParameters() throws IllegalArgumentException, InjectionException; } 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 66425d87b72..041a7133e63 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,7 @@ 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 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/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java index 0e93d9bb60e..9dc7b9cbab3 100644 --- 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 @@ -33,6 +33,9 @@ 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 = null; + static { URL url = JmlScriptTest.class.getResource("jml/project.key"); try { @@ -90,9 +93,13 @@ private static Parameters readParams(Path path) throws IOException { public static Stream filesProvider() throws URISyntaxException, IOException { URL jmlUrl = JmlScriptTest.class.getResource("jml"); - return Files.list(Paths.get(jmlUrl.toURI())) - .filter(p -> p.toString().endsWith(".java")) - .map(p -> Arguments.of(p, p.getFileName().toString())); + if (ONLY_CASE != null) { + return Stream.of(Arguments.of(Paths.get(jmlUrl.toURI()).resolve(ONLY_CASE), "single specified 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 { 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 index aeb6f6b356d..92bd352ddf2 100644 --- 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 @@ -6,8 +6,9 @@ void test() { int x = 42; /*@ assert x == 42 \by { obtain int y = 41; - assert y+1 == 42; + 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..5bd1ba3b553 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainFromGoal.java @@ -0,0 +1,18 @@ +//! deleteTmpDir : false + +class Test { + + //@ static 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 + } */ + } + +} From 2710c8eaa7e1a9b1bb80c49502c30413c932e648 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 2 Oct 2025 18:44:51 +0200 Subject: [PATCH 42/77] pimping the document generation for proof script commands --- .../uka/ilkd/key/scripts/AbstractCommand.java | 4 + .../de/uka/ilkd/key/scripts/AutoCommand.java | 35 ++-- .../de/uka/ilkd/key/scripts/CutCommand.java | 17 +- .../de/uka/ilkd/key/scripts/EchoCommand.java | 9 +- .../de/uka/ilkd/key/scripts/FocusCommand.java | 17 ++ .../ilkd/key/scripts/InstantiateCommand.java | 34 ++-- .../de/uka/ilkd/key/scripts/LeaveCommand.java | 4 + .../uka/ilkd/key/scripts/ObtainCommand.java | 10 +- .../key/scripts/OneStepSimplifierCommand.java | 11 ++ .../ilkd/key/scripts/ProofScriptCommand.java | 23 ++- .../ilkd/key/scripts/ProofScriptEngine.java | 2 +- .../de/uka/ilkd/key/scripts/RuleCommand.java | 24 ++- .../de/uka/ilkd/key/scripts/SMTCommand.java | 16 ++ .../uka/ilkd/key/scripts/SelectCommand.java | 41 ++++- .../de/uka/ilkd/key/scripts/SkipCommand.java | 7 +- .../key/scripts/meta/ArgumentsLifter.java | 154 +++++++++++------- .../ilkd/key/scripts/meta/Documentation.java | 1 + .../key/scripts/meta/ProofScriptArgument.java | 13 ++ .../key/scripts/DocumentationGenerator.java | 104 ++++++++++++ 19 files changed, 405 insertions(+), 121 deletions(-) create mode 100644 key.core/src/test/java/de/uka/ilkd/key/scripts/DocumentationGenerator.java 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 55fe6f0777e..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 @@ -104,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/AutoCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AutoCommand.java index 9dfa98d7358..8cd83fa1f52 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 @@ -161,9 +161,10 @@ 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. + @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. @@ -172,46 +173,48 @@ private void setupFocussedBreakpointStrategy(final String maybeMatchesRegEx, public static class Parameters { // @ 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") - public @Nullable Boolean modelSearch; + @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.") - public @Nullable Boolean expandQueries; + @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.""") - public @Nullable Boolean classAxioms; + 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") @Documentation(""" Enable dependency reasoning. In modular reasoning, the value of symbols may stay the same, \ without that its definition is known. May be an enabler, may be a showstopper.""") - public @Nullable Boolean dependencies; + public boolean dependencies; } private static final class OriginalValue { 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 465c90e41a9..7ef35b44fe7 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 @@ -11,6 +11,7 @@ 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; @@ -38,15 +39,6 @@ public List getAliases() { return List.of(getName(), "assert"); } - @Override - public String getDocumentation() { - return """ - CutCommand has as script command name "cut" - - As parameters: - * a formula with the id "#2"""; - } - @Override public void execute(ScriptCommandAst arguments) throws ScriptException, InterruptedException { var args = state().getValueInjector().inject(new Parameters(), arguments); @@ -64,8 +56,15 @@ static void execute(EngineState state, Parameters args) throws ScriptException { 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/EchoCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/EchoCommand.java index 306a7d43c32..1095212d468 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 @@ -6,6 +6,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/FocusCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/FocusCommand.java index 6f9b1f724a2..df279f4afea 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 @@ -15,6 +15,7 @@ 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; @@ -48,8 +49,24 @@ 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 + @Documentation("The sequent containing the formulas to keep. It may contain placeholder symbols.") public @MonotonicNonNull Sequent toKeep; } 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 6dd0c2e7f96..45424416ce6 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 @@ -222,34 +222,36 @@ 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("Instantiate a universally quantified formula (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.\n" - + "'with' must be specified.") + @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. Either this or 'var' must be given.") + @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. Either this or 'formula' must be given.") + @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. Default is 1 (the first).") + @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.") + @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; 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..a7d3689e61f 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 @@ -6,9 +6,13 @@ 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/ObtainCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/ObtainCommand.java index f855070bf1c..2576e9250d5 100644 --- 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 @@ -155,10 +155,16 @@ private static SchemaVariable getSV(ImmutableSet schemaVars, Str throw new NoSuchElementException("No schema variable with name " + name); } - @Documentation("TODO.") + @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 instantiated variable.") + @Documentation("Name of the variable to be instantiated.") public @MonotonicNonNull String var; @Option(value = "such_that") 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 index 741b6eeee58..e9925d75949 100644 --- 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 @@ -6,6 +6,7 @@ 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.Option; import org.key_project.logic.PosInTerm; @@ -57,10 +58,20 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte } } + @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; } 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 4259296d086..1fd2bceb78a 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 @@ -49,7 +49,7 @@ public ProofScriptEngine(Proof proof) { this.stateMap = new EngineState(proof, this); } - private static Map loadCommands() { + static Map loadCommands() { Map result = new HashMap<>(); var loader = ServiceLoader.load(ProofScriptCommand.class); 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 b96b6609476..ceae3c429f1 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 @@ -392,34 +392,46 @@ private List filterList(Services services, Parameters p, return matchingApps; } - @Documentation("This command can be used to apply a calculus rule to the currently active open goal.") + @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") - @Documentation("Term on which the rule should be applied to (matching the 'find' clause of the rule).") + @Documentation("Term on which the rule should be applied to (matching the 'find' clause of the rule). " + + "This may contain placeholders.") public @Nullable JTerm on; @Option(value = "formula") - @Documentation("Top-level formula in which the term appears.") + @Documentation("Top-level formula in which the term appears. This may contain placeholders.") public @Nullable JTerm formula; @Option(value = "occ") - @Documentation("Occurrence number if more than one occurrence matches.") + @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.") + @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; @OptionalVarargs(as = JTerm.class, prefix = "inst_") - @Documentation("Instantiations for schema variables used in the rule.") + @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..96f94da6d87 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/SelectCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SelectCommand.java index 650ef75542d..71bb5a35d40 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,8 +12,11 @@ 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; import org.key_project.prover.sequent.Sequent; @@ -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,48 @@ 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/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/meta/ArgumentsLifter.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java index cced4bcc96d..67dc24ea8fe 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.AbstractCommand; +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,21 @@ 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()) { @@ -64,8 +67,9 @@ public static String generateCommandUsage(String commandName, Class parameter if (meta.isOptionalVarArgs()) { sb.append("%s...".formatted(meta.getName())); } - - sb.append(meta.isRequired() ? '>' : ']'); + + if(!meta.isRequired() || meta.isFlag()) + sb.append("]"); } @@ -76,48 +80,60 @@ 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 +141,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.getField().getType().getSimpleName(), meta.getDocumentation())); } @@ -137,42 +152,71 @@ 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/Documentation.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/Documentation.java index a4fe62a1766..e3c7a1f62f4 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,5 @@ 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/ProofScriptArgument.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ProofScriptArgument.java index 66939fadcfc..02b11bf41e9 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,17 @@ 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/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..f747d4da417 --- /dev/null +++ b/key.core/src/test/java/de/uka/ilkd/key/scripts/DocumentationGenerator.java @@ -0,0 +1,104 @@ +package de.uka.ilkd.key.scripts; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +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.util.KeYResourceManager; +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; + +import java.io.File; +import java.io.FileNotFoundException; +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.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class DocumentationGenerator { + + 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(); + + Collection commands = ProofScriptEngine.loadCommands().values(); + Map> commandsByCategory = new TreeMap<>(); + + for (ProofScriptCommand command : commands) { + String category = command.getCategory(); + if (category == null) { + category = "Uncategorized"; + } + commandsByCategory.computeIfAbsent(category, k -> new ArrayList<>()).add(command); + } + + 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() { + String branch = KeYResourceManager.getManager().getBranch(); + String version = KeYResourceManager.getManager().getVersion(); + String sha1 = KeYResourceManager.getManager().getSHA1(); + + 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) { + Set alreadyListed = new HashSet<>(); + System.out.println("\n## Category *" + category + "*\n"); + for (ProofScriptCommand command : proofScriptCommands) { + if(alreadyListed.contains(command)) + continue; + alreadyListed.add(command); + System.out.println("


    \n"); + System.out.println("### Command `" + command.getName() + "`\n"); + System.out.println(command.getDocumentation() + "\n"); + if(command.getAliases().size() > 1) { + System.out.println("#### Aliases:\n" + String.join(", ", command.getAliases()) + "\n"); + } + } + } +} From 5d3fcc16f946ff2f56c7881a676ef8b9f6320e2e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 3 Oct 2025 00:04:07 +0200 Subject: [PATCH 43/77] lots of script documentation --- .../uka/ilkd/key/scripts/ActivateCommand.java | 12 +++--- .../de/uka/ilkd/key/scripts/AllCommand.java | 19 +++++----- .../key/scripts/AssertOpenGoalsCommand.java | 2 +- .../uka/ilkd/key/scripts/AssumeCommand.java | 14 ++----- .../de/uka/ilkd/key/scripts/AxiomCommand.java | 5 +++ .../de/uka/ilkd/key/scripts/CheatCommand.java | 13 +++---- .../scripts/DependencyContractCommand.java | 19 +++++++++- .../de/uka/ilkd/key/scripts/ExitCommand.java | 10 ++--- .../de/uka/ilkd/key/scripts/HideCommand.java | 8 +++- .../ilkd/key/scripts/JavascriptCommand.java | 37 +++++++++++++++++++ .../de/uka/ilkd/key/scripts/MacroCommand.java | 22 ++++++++++- .../uka/ilkd/key/scripts/SaveInstCommand.java | 6 +++ .../ilkd/key/scripts/SaveNewNameCommand.java | 15 +++++++- .../ilkd/key/scripts/SchemaVarCommand.java | 5 +++ .../uka/ilkd/key/scripts/ScriptCommand.java | 7 ++++ .../uka/ilkd/key/scripts/SetEchoCommand.java | 7 +++- .../key/scripts/SetFailOnClosedCommand.java | 2 + .../uka/ilkd/key/scripts/UnhideCommand.java | 8 +++- .../key/scripts/meta/ArgumentsLifter.java | 2 +- .../key/scripts/DocumentationGenerator.java | 7 +++- 20 files changed, 174 insertions(+), 46 deletions(-) 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/AllCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java index d185a3af2c0..aad15803e3f 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/AssertOpenGoalsCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/AssertOpenGoalsCommand.java index ba84205242e..f4aa0ff05ab 100755 --- 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 @@ -45,7 +45,7 @@ public String getName() { /** * The Assigned parameters (currently only the passed goals). */ - @Documentation(""" + @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. 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..c997292a3cb 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 @@ -34,16 +34,6 @@ 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); @@ -57,6 +47,10 @@ 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. + """) public static class FormulaParameter { @Argument @Documentation("The formula to be assumed.") 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..5da8270eb51 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 @@ -10,6 +10,7 @@ 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/CheatCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/CheatCommand.java index 65b697d1219..e3194efcb71 100644 --- 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 @@ -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.Documentation; import org.key_project.logic.ChoiceExpr; import org.key_project.logic.Name; import org.key_project.prover.rules.ApplicationRestriction; @@ -19,6 +20,11 @@ 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; @@ -38,13 +44,6 @@ public String getName() { return "cheat"; } - @Override - public String getDocumentation() { - return "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."; - } - @Override public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst ast, EngineState state) 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 index 99c1deaaf5a..587b7bf5799 100644 --- 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 @@ -11,8 +11,10 @@ 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.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.key_project.logic.PosInTerm; import org.key_project.logic.Term; import org.key_project.prover.sequent.PosInOccurrence; @@ -23,6 +25,10 @@ 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() { @@ -104,10 +110,21 @@ private void apply(Goal goal, UseDependencyContractApp ruleApp, Parameters argum 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 JTerm 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/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/HideCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/HideCommand.java index 6f37e696469..77ef42f4302 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 @@ -11,6 +11,7 @@ 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; import org.key_project.logic.Term; @@ -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,14 @@ 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/JavascriptCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/JavascriptCommand.java index d3bb13607c9..77ef4459391 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 @@ -13,10 +13,29 @@ 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,25 @@ 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/MacroCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/MacroCommand.java index bd7763f428f..cffb75e9f57 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 @@ -19,13 +19,18 @@ import de.uka.ilkd.key.scripts.meta.Option; import de.uka.ilkd.key.scripts.meta.OptionalVarargs; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.key_project.logic.PosInTerm; import org.key_project.prover.engine.TaskStartedInfo; import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.prover.sequent.Sequent; 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 +169,22 @@ 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 +196,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/SaveInstCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SaveInstCommand.java index 61bf7156e1f..2b39adbfaea 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 @@ -9,6 +9,7 @@ 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; import org.key_project.logic.op.sv.SchemaVariable; @@ -24,6 +25,11 @@ * * @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..42d0c818376 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,20 @@ 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..06ac00c5099 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 @@ -10,6 +10,7 @@ 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 50a6eff3b2c..978edf073a2 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 @@ -9,10 +9,15 @@ 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; } 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..ff84be60a72 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 @@ -6,11 +6,16 @@ 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 85aeb15dbe6..3ca852901ea 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 @@ -6,6 +6,7 @@ import de.uka.ilkd.key.scripts.meta.Argument; +import de.uka.ilkd.key.scripts.meta.Documentation; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** @@ -19,6 +20,7 @@ * @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/UnhideCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/UnhideCommand.java index b7622eb9a78..c4d902aa13a 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 @@ -11,6 +11,7 @@ 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; import org.key_project.prover.proof.rulefilter.TacletFilter; @@ -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,12 @@ 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/meta/ArgumentsLifter.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ArgumentsLifter.java index 67dc24ea8fe..e37627457ff 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 @@ -143,7 +143,7 @@ public static String extractDocumentation(String command, Class commandClazz, if (meta.isOptionalVarArgs()) { sb.append("* `%s...`: *(options prefixed by `%s`, type %s)*:
    %s".formatted( meta.getName(), meta.getOptionalVarArgs().prefix(), - meta.getField().getType().getSimpleName(), + meta.getOptionalVarArgs().as().getSimpleName(), meta.getDocumentation())); } 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 index f747d4da417..fab732e5206 100644 --- 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 @@ -64,7 +64,11 @@ private static void printHeader() { String version = KeYResourceManager.getManager().getVersion(); String sha1 = KeYResourceManager.getManager().getSHA1(); - System.out.printf(""" + // 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. @@ -87,6 +91,7 @@ private static void printHeader() { } private static void listCategory(String category, List proofScriptCommands) { + proofScriptCommands.sort(Comparator.comparing(ProofScriptCommand::getName)); Set alreadyListed = new HashSet<>(); System.out.println("\n## Category *" + category + "*\n"); for (ProofScriptCommand command : proofScriptCommands) { From 382f4ce88f33ee3eb90e34b05e23451863271d4c Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 3 Oct 2025 02:09:45 +0200 Subject: [PATCH 44/77] fixing a bug regarding proof script application --- .../java/de/uka/ilkd/key/gui/ProofScriptWorker.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 14cc9adcda5..e218efc1623 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 @@ -23,6 +23,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; @@ -47,7 +48,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(); @@ -76,13 +78,13 @@ public ProofScriptWorker(KeYMediator mediator, KeyAst.ProofScript script, this.mediator = mediator; this.script = script; this.initiallySelectedGoal = initiallySelectedGoal; - engine = new ProofScriptEngine(initiallySelectedGoal.proof()); - engine.setInitiallySelectedGoal(initiallySelectedGoal); } @Override protected @Nullable Object doInBackground() throws Exception { try { + engine = new ProofScriptEngine(mediator.getSelectedProof()); + engine.setInitiallySelectedGoal(initiallySelectedGoal); engine.setCommandMonitor(observer); engine.execute(mediator.getUI(), script); } catch (InterruptedException ex) { @@ -173,7 +175,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()); From 835497a5f8a2bdd861f714b898804355d846c31e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 3 Oct 2025 10:37:57 +0200 Subject: [PATCH 45/77] repairing some weird self variable treatment in SpecStatement --- .../ilkd/key/macros/ApplyScriptsMacro.java | 35 ++++++++++++++++--- .../ilkd/key/scripts/jml/ObtainFromGoal.java | 2 +- 2 files changed, 32 insertions(+), 5 deletions(-) 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 index 9bd55b96f3e..d7557e761e4 100644 --- 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 @@ -13,8 +13,10 @@ 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.JFunction; import de.uka.ilkd.key.logic.op.LocationVariable; import de.uka.ilkd.key.logic.op.UpdateApplication; @@ -33,8 +35,9 @@ 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.SortedOperator; +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; @@ -126,6 +129,16 @@ private static JmlAssert getJmlAssert(Node node) { return null; } + 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) @@ -146,7 +159,7 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, "Running attached script from goal " + goal.node().serialNr(), 0)); KeyAst.JMLProofScript proofScript = jmlAssert.getAssertionProof(); - Map termMap = getTermMap(jmlAssert, proof.getServices()); + 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)); JTerm update = getUpdate(goal); @@ -180,14 +193,17 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, } - private Map getTermMap(JmlAssert jmlAssert, Services services) { + 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 = jmlspec.terms().tail(); + 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(); @@ -197,6 +213,17 @@ private Map getTermMap(JmlAssert jmlAssert, Services s 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) { 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 index 5bd1ba3b553..4f7350b56f1 100644 --- 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 @@ -2,7 +2,7 @@ class Test { - //@ static model int f(int arg); + //@ model int f(int arg); //@ ensures true; void test() { From 12098475c75e0177ffdb44b75265077c24da8d9c Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 19 Jul 2024 17:46:43 +0200 Subject: [PATCH 46/77] introducing "auto add_*" to scripts. --- .../key/scripts/AdditionalRulesStrategy.java | 116 ++++++++++++++++++ .../de/uka/ilkd/key/scripts/AutoCommand.java | 15 +++ 2 files changed, 131 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/AdditionalRulesStrategy.java 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..27aad464413 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/AdditionalRulesStrategy.java @@ -0,0 +1,116 @@ +package de.uka.ilkd.key.scripts; + +import org.jspecify.annotations.Nullable; +import org.key_project.prover.rules.Rule; +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.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.util.collection.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +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 int DEFAULT_PRIORITY = 1000; + + private final List> additionalRules; + + public AdditionalRulesStrategy(Strategy delegate, String additionalRules) { + super(delegate); + this.additionalRules = parseAddRules(additionalRules); + } + + private List> parseAddRules(String additionalRules) { + List> result = new ArrayList<>(); + for (String entry : additionalRules.trim().split(" *, *")) { + String[] parts = entry.split(" *= *", 2); + int prio; + if (parts.length == 2) { + String prioStr = parts[1]; + prioStr = TRANSLATIONS.getOrDefault(prioStr, prioStr); + try { + prio = 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; + } + 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 NumberRuleAppCost.create(cost.get()); + } + + if (rule instanceof Taclet taclet) { + for (RuleSet rs : taclet.getRuleSets()) { + String rname = rs.name().toString(); + cost = lookup(rname); + if(cost.isPresent()) { + return NumberRuleAppCost.create(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; + } + + +} \ No newline at end of file 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 8cd83fa1f52..e9699a5647f 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 @@ -14,6 +14,7 @@ import de.uka.ilkd.key.prover.impl.ApplyStrategy; 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; @@ -92,6 +93,11 @@ 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)); + } + // Give some feedback applyStrategy.addProverTaskObserver(uiControl); @@ -113,6 +119,7 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx activeStrategyProperties.setProperty(ov.settingName, ov.oldValue); } } + state.getProof().setActiveStrategy(originalStrategy); SetCommand.updateStrategySettings(state(), activeStrategyProperties); } } @@ -215,6 +222,14 @@ may be a showstopper (if expansion increases the complexity on the sequent too m Enable dependency reasoning. In modular reasoning, the value of symbols may stay the same, \ without that its definition is known. May be an enabler, may be a showstopper.""") public boolean dependencies; + + @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. + """) + public @Nullable String additionalRules; } private static final class OriginalValue { From 0dac52597464631e78dff17a5ccaa30194f16f8f Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 3 Oct 2025 14:02:25 +0200 Subject: [PATCH 47/77] smaller fixes in proof script treatment --- .../java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java | 10 +++++++--- .../ilkd/key/proof/mgt/SpecificationRepository.java | 1 + .../java/de/uka/ilkd/key/scripts/ExpandDefCommand.java | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) 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 index d7557e761e4..56c24a237f1 100644 --- 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 @@ -170,6 +170,8 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, pse.getStateMap().putUserData("jml.obtainVarMap", obtainMap); pse.getStateMap().getValueInjector().addConverter(JTerm.class, ObtainAwareTerm.class, oat -> 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"))); @@ -335,11 +337,13 @@ private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map Date: Fri, 3 Oct 2025 14:01:51 +0200 Subject: [PATCH 48/77] The Boyer-Moore example now runs on scripts --- .../proof/runallproofs/ProofCollections.java | 15 ++-- key.ui/examples/heap/BoyerMoore/BM.bm.key | 86 +++++++++++++++++++ .../heap/BoyerMoore/BM.count.accessible.key | 85 ++++++++++++++++++ key.ui/examples/heap/BoyerMoore/BM.count.key | 84 ++++++++++++++++++ .../examples/heap/BoyerMoore/BM.monoLemma.key | 83 ++++++++++++++++++ .../examples/heap/BoyerMoore/BoyerMoore.key | 2 +- key.ui/examples/heap/BoyerMoore/README.txt | 5 +- .../heap/BoyerMoore/src/BoyerMoore.java | 12 +++ 8 files changed, 360 insertions(+), 12 deletions(-) create mode 100644 key.ui/examples/heap/BoyerMoore/BM.bm.key create mode 100644 key.ui/examples/heap/BoyerMoore/BM.count.accessible.key create mode 100644 key.ui/examples/heap/BoyerMoore/BM.count.key create mode 100644 key.ui/examples/heap/BoyerMoore/BM.monoLemma.key 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.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; From eeaf1f38e495fd99666ed8349fbb922f3fd8e105 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 3 Oct 2025 19:13:09 +0200 Subject: [PATCH 49/77] improved document generation --- .../key/scripts/DocumentationGenerator.java | 67 ++++++++----------- 1 file changed, 28 insertions(+), 39 deletions(-) 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 index fab732e5206..7b42ca2e2b1 100644 --- 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 @@ -1,32 +1,15 @@ package de.uka.ilkd.key.scripts; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -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.util.KeYResourceManager; -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; - -import java.io.File; + import java.io.FileNotFoundException; -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.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class DocumentationGenerator { + private static String branch; + private static String sha1; + public static void main(String[] args) throws FileNotFoundException { if(args.length > 0) { @@ -36,15 +19,15 @@ public static void main(String[] args) throws FileNotFoundException { printHeader(); - Collection commands = ProofScriptEngine.loadCommands().values(); - Map> commandsByCategory = new TreeMap<>(); + Set> commands = ProofScriptEngine.loadCommands().entrySet(); + Map>> commandsByCategory = new TreeMap<>(); - for (ProofScriptCommand command : commands) { - String category = command.getCategory(); + for (Map.Entry entry : commands) { + String category = entry.getValue().getCategory(); if (category == null) { category = "Uncategorized"; } - commandsByCategory.computeIfAbsent(category, k -> new ArrayList<>()).add(command); + commandsByCategory.computeIfAbsent(category, k -> new ArrayList<>()).add(entry); } List categories = new ArrayList<>(commandsByCategory.keySet()); @@ -60,9 +43,9 @@ public static void main(String[] args) throws FileNotFoundException { } private static void printHeader() { - String branch = KeYResourceManager.getManager().getBranch(); + branch = KeYResourceManager.getManager().getBranch(); String version = KeYResourceManager.getManager().getVersion(); - String sha1 = KeYResourceManager.getManager().getSHA1(); + sha1 = KeYResourceManager.getManager().getSHA1(); // This gets too technical. But this is for the key-docs repository. ... System.out.printf(""" @@ -90,19 +73,25 @@ private static void printHeader() { """, new Date(), branch, version, sha1); } - private static void listCategory(String category, List proofScriptCommands) { - proofScriptCommands.sort(Comparator.comparing(ProofScriptCommand::getName)); - Set alreadyListed = new HashSet<>(); + private static void listCategory(String category, List> proofScriptCommands) { + proofScriptCommands.sort(Map.Entry.comparingByKey()); System.out.println("\n## Category *" + category + "*\n"); - for (ProofScriptCommand command : proofScriptCommands) { - if(alreadyListed.contains(command)) - continue; - alreadyListed.add(command); + for (Map.Entry entry : proofScriptCommands) { System.out.println("
    \n"); - System.out.println("### Command `" + command.getName() + "`\n"); - System.out.println(command.getDocumentation() + "\n"); - if(command.getAliases().size() > 1) { - System.out.println("#### Aliases:\n" + String.join(", ", command.getAliases()) + "\n"); + if(entry.getKey().equals(entry.getValue().getName())) { + ProofScriptCommand command = entry.getValue(); + String link = "main".equals(branch) ? "main" : sha1; + System.out.printf("### [Source](https://github.com/KeYProject/key/blob/%s/key.core/src/main/java/%s.java)", + link, + command.getClass().getName().replace('.', '/') ); + System.out.println(" Command `" + command.getName() + "`\n\n"); + 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"); } } } From 15bd146d35b11060378a2b8239d9d876de884531 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 4 Oct 2025 01:16:36 +0200 Subject: [PATCH 50/77] more proof script power, update inlining. --- .../ilkd/key/macros/ApplyScriptsMacro.java | 49 +++++++++++++----- .../key/scripts/OneStepSimplifierCommand.java | 50 ++++++++++++------- .../ilkd/key/scripts/meta/ValueInjector.java | 10 ++-- .../key/scripts/DocumentationGenerator.java | 4 +- .../uka/ilkd/key/scripts/JmlScriptTest.java | 22 +++++++- .../key/scripts/jml/ObtainWithUpdates.java | 23 +++++++++ 6 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/ObtainWithUpdates.java 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 index 56c24a237f1..394c32c4e0d 100644 --- 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 @@ -17,9 +17,7 @@ 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.JFunction; -import de.uka.ilkd.key.logic.op.LocationVariable; -import de.uka.ilkd.key.logic.op.UpdateApplication; +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.*; @@ -120,15 +118,34 @@ private static JmlAssert getJmlAssert(Node node) { return null; } - private static @Nullable JTerm getUpdate(Goal goal) { + private static @Nullable OpReplacer getUpdateReplacer(Goal goal) { RuleApp ruleApp = goal.node().parent().getAppliedRuleApp(); Term appliedOn = ruleApp.posInOccurrence().subTerm(); if (appliedOn.op() instanceof UpdateApplication) { - return UpdateApplication.getUpdate((JTerm) appliedOn); + 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(); @@ -162,9 +179,9 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, 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)); - JTerm update = getUpdate(goal); + @Nullable OpReplacer updateReplacer = getUpdateReplacer(goal); List renderedProof = - renderProof(proofScript, termMap, update, proof.getServices()); + renderProof(proofScript, termMap, updateReplacer, proof.getServices()); ProofScriptEngine pse = new ProofScriptEngine(proof); pse.setInitiallySelectedGoal(goal); pse.getStateMap().putUserData("jml.obtainVarMap", obtainMap); @@ -235,7 +252,7 @@ private Map makeObtainVarMap(ImmutableList renderProof(KeyAst.JMLProofScript script, - Map termMap, JTerm update, Services services) throws ScriptException { + 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())); @@ -248,9 +265,13 @@ private static List renderProof(KeyAst.JMLProofScript script, } private static List renderProofCmd(ProofCmdContext ctx, - Map termMap, JTerm update, Services services) throws ScriptException { + Map termMap, + @Nullable OpReplacer update, Services services) throws ScriptException { List result = new ArrayList<>(); + // Prepare by resolving the update + result.add(new ScriptCommandAst("oss", Map.of("recentOnly", true), List.of())); + // Push the current branch context result.add(new ScriptCommandAst("branches", Map.of(), List.of("push"))); @@ -290,7 +311,8 @@ private static List renderProofCmd(ProofCmdContext ctx, return result; } - private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map termMap, JTerm update, Services services) throws ScriptException { + 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()) { @@ -313,7 +335,7 @@ private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map termMap, JTerm update, Services services) { + 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()) { @@ -334,7 +356,7 @@ private static ScriptCommandAst renderObtainCommand(ProofCmdContext ctx, Map builtins = goal.ruleAppIndex().getBuiltInRules(goal, - new PosInOccurrence(sf, PosInTerm.getTopLevel(), true)); - for (IBuiltInRuleApp builtin : builtins) { - if (builtin instanceof OneStepSimplifierRuleApp) { - goal.apply(builtin); - } - } - } + + 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 (arguments.succedent) { - for (SequentFormula sf : goal.sequent().succedent()) { + 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(), false)); + new PosInOccurrence(sf, PosInTerm.getTopLevel(), inAntec)); for (IBuiltInRuleApp builtin : builtins) { if (builtin instanceof OneStepSimplifierRuleApp) { goal.apply(builtin); @@ -56,9 +67,9 @@ public void execute(ScriptCommandAst command) throws ScriptException, Interrupte } } } - } - @Documentation(category = "Fundamental", value = """ + + @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. @@ -74,5 +85,10 @@ public static class Parameters { "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/meta/ValueInjector.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/meta/ValueInjector.java index 8300997d66e..d4a33e16384 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 @@ -140,10 +140,10 @@ public T inject(T obj, ScriptCommandAst arguments) .findAny(); if (unhandled.isPresent()) { throw new UnknownArgumentException(String.format( - "Unknown argument %s (with value %s) was provided. For command class: '%s'", + "Unknown option %s (with value %s) was provided. For command: '%s'", unhandled.get(), arguments.namedArgs().get(unhandled.get()), - obj.getClass().getName())); + arguments.commandName())); } Optional unhandledPos = IntegerUtil.indexRangeOf(arguments.positionalArgs()) @@ -151,11 +151,9 @@ public T inject(T obj, ScriptCommandAst arguments) .filter(it -> !handledOptions.contains(it)) .findAny(); if (unhandledPos.isPresent()) { - long count = handledOptions.stream().filter(it -> it instanceof Integer).count(); throw new UnknownArgumentException(String.format( - "Unexpected positional argument at index %d was provided. " + - "Expected (at most) %d positional arguments. For command class: '%s'", - unhandledPos.get(), count, obj.getClass().getName())); + "Unexpected positional argument or flag provided: %s", + arguments.positionalArgs().get(unhandledPos.get()))); } if (obj instanceof VerifyableParameters vp) { 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 index 7b42ca2e2b1..a70ab5de2ed 100644 --- 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 @@ -81,10 +81,10 @@ private static void listCategory(String category, List[Source](https://github.com/KeYProject/key/blob/%s/key.core/src/main/java/%s.java)", + 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 `" + command.getName() + "`\n\n"); System.out.println(command.getDocumentation() + "\n"); if (command.getAliases().size() > 1) { System.out.println("#### Aliases:\n" + String.join(", ", command.getAliases()) + "\n"); 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 index 9dc7b9cbab3..75e91b84328 100644 --- 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 @@ -7,6 +7,8 @@ import de.uka.ilkd.key.control.UserInterfaceControl; import de.uka.ilkd.key.nparser.KeyAst; import de.uka.ilkd.key.proof.io.ProblemLoaderControl; +import de.uka.ilkd.key.proof.io.ProofSaver; +import de.uka.ilkd.key.util.KeYConstants; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -24,6 +26,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; +import java.util.Map; import java.util.logging.LogManager; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -35,6 +38,7 @@ public class JmlScriptTest { // Set this to a specific case to only run that case for debugging private static final String ONLY_CASE = null; + private static final boolean SAVE_PROOF = true; static { URL url = JmlScriptTest.class.getResource("jml/project.key"); @@ -57,12 +61,25 @@ public void testJmlScript(Path path, String identifier) throws Exception { 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 { @@ -70,7 +87,7 @@ public void testJmlScript(Path path, String identifier) throws Exception { } } finally { // Uncomment the following line to delete the temporary directory after the test - if(params.deleteTmpDir) { + if(params.deleteTmpDir && !SAVE_PROOF) { LOGGER.info("Deleting temporary directory: {}", tmpDir); Files.walk(tmpDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } else { @@ -81,7 +98,7 @@ public void testJmlScript(Path path, String identifier) throws Exception { } private static Parameters readParams(Path path) throws IOException { - String input = Files.lines(path).filter(l -> l.startsWith("//!")).map(l -> l.substring(3).trim()) + 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(); @@ -107,6 +124,7 @@ static class Parameters { public String method; public String exception; public boolean deleteTmpDir = true; + public Map settings; } 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 + } */ + } + +} From 2382655d55eb47227b6e263805ddef184bdfb118 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 4 Oct 2025 03:00:58 +0200 Subject: [PATCH 51/77] making sure that cuts work with boolean terms instead of formulas. --- .../de/uka/ilkd/key/scripts/CutCommand.java | 5 ++-- .../key/scripts/jml/AssertedModelMethod.java | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AssertedModelMethod.java 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 7ef35b44fe7..cc89557ddcc 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 @@ -51,8 +51,9 @@ 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); } 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 + } */ + } + +} From 5e8810d29858caf6cf43926dd8f3fa85e6be243b Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sun, 5 Oct 2025 18:03:25 +0200 Subject: [PATCH 52/77] better symbex-only machine --- .../ilkd/key/macros/ApplyScriptsMacro.java | 5 +- .../uka/ilkd/key/macros/ScriptAwareMacro.java | 2 +- .../ilkd/key/macros/ScriptAwarePrepMacro.java | 2 +- .../macros/SymbolicExecutionOnlyMacro.java | 149 ++++++++++++++++++ .../de.uka.ilkd.key.macros.ProofMacro | 1 + ...licExecutionOnlyMacro.admittedRuleSets.txt | 8 + ...mbolicExecutionOnlyMacro.admittedRules.txt | 4 + 7 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.java create mode 100644 key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRuleSets.txt create mode 100644 key.core/src/main/resources/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.admittedRules.txt 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 index 394c32c4e0d..e9987ca40f1 100644 --- 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 @@ -256,6 +256,8 @@ private static List renderProof(KeyAst.JMLProofScript script, 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)); } @@ -269,9 +271,6 @@ private static List renderProofCmd(ProofCmdContext ctx, @Nullable OpReplacer update, Services services) throws ScriptException { List result = new ArrayList<>(); - // Prepare by resolving the update - result.add(new ScriptCommandAst("oss", Map.of("recentOnly", true), List.of())); - // Push the current branch context result.add(new ScriptCommandAst("branches", Map.of(), List.of("push"))); 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 index 320e7d57c22..397118f2ca8 100644 --- 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 @@ -34,7 +34,7 @@ */ public class ScriptAwareMacro extends SequentialProofMacro { - private final ProofMacro autoMacro = new FinishSymbolicExecutionMacro(); + private final ProofMacro autoMacro = new SymbolicExecutionOnlyMacro(); // FinishSymbolicExecutionMacro(); private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(new TryCloseMacro()); @Override 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 index 175f5fa8e51..d2e40deb16b 100644 --- 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 @@ -34,7 +34,7 @@ */ public class ScriptAwarePrepMacro extends SequentialProofMacro { - private final ProofMacro autoMacro = new FinishSymbolicExecutionMacro(); + private final ProofMacro autoMacro = new SymbolicExecutionOnlyMacro(); // new FinishSymbolicExecutionMacro(); private final ApplyScriptsMacro applyMacro = new ApplyScriptsMacro(null); @Override 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..c47a4b958f1 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/macros/SymbolicExecutionOnlyMacro.java @@ -0,0 +1,149 @@ +/* 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.TermLabelVisibilityManager; +import de.uka.ilkd.key.logic.op.ObserverFunction; +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.jspecify.annotations.NonNull; +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.strategy.costbased.NumberRuleAppCost; +import org.key_project.util.Streams; +import org.key_project.util.java.StringUtil; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 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; + } + + private static boolean isUpdatedModality(Term term) { + while(term.op() instanceof UpdateApplication) { + term = term.sub(1); + } + return term.op() instanceof Modality; + } + + private static class FilterSymbexStrategy extends FilterStrategy { + + private static final Name NAME = new Name(FilterSymbexStrategy.class.getSimpleName()); + + public FilterSymbexStrategy(Strategy<@NonNull Goal> delegate) { + super(delegate); + } + + @Override + public Name name() { + return NAME; + } + + @Override + public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { + return isAdmittedRule(app) && super.isApprovedApp(app, pio, goal); + } + + @Override + public boolean isStopAtFirstNonCloseableGoal() { + return false; + } + } + +} 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 313dd07613b..20f77f0e83f 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 @@ -32,3 +32,4 @@ 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/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 From 4024d29acfee88cc9baf1e1b683e0ff5b55dfc9c Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sun, 5 Oct 2025 18:04:11 +0200 Subject: [PATCH 53/77] Allowing "auto only: ..." --- .../key/scripts/AdditionalRulesStrategy.java | 10 ++++++-- .../de/uka/ilkd/key/scripts/AutoCommand.java | 25 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) 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 index 27aad464413..bb0609b0653 100644 --- 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 @@ -29,10 +29,12 @@ class AdditionalRulesStrategy extends FilterStrategy { private static final int DEFAULT_PRIORITY = 1000; private final List> additionalRules; + private final boolean exclusive; - public AdditionalRulesStrategy(Strategy delegate, String additionalRules) { + public AdditionalRulesStrategy(Strategy delegate, String additionalRules, boolean exclusive) { super(delegate); this.additionalRules = parseAddRules(additionalRules); + this.exclusive = exclusive; } private List> parseAddRules(String additionalRules) { @@ -77,7 +79,11 @@ public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { if (localCost != null) { return true; } - return super.isApprovedApp(app, pio, goal); + if (exclusive) { + return false; + } else { + return super.isApprovedApp(app, pio, goal); + } } private @Nullable RuleAppCost computeLocalCost(Rule rule) { 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 e9699a5647f..c6199a2c88a 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 @@ -95,7 +95,10 @@ public void execute(ScriptCommandAst args) throws ScriptException, InterruptedEx final Strategy originalStrategy = state.getProof().getActiveStrategy(); if (arguments.additionalRules != null) { - state.getProof().setActiveStrategy(new AdditionalRulesStrategy(originalStrategy, arguments.additionalRules)); + 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 @@ -177,7 +180,7 @@ private void setupFocussedBreakpointStrategy(final String maybeMatchesRegEx, 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("*Deprecated*. Apply the strategy on all open goals. There is a better syntax for that now.") @@ -228,8 +231,26 @@ may be a showstopper (if expansion increases the complexity on the sequent too m 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."); + } + } } private static final class OriginalValue { From 5b3311e1f3be7ea9c20079f673d904e7451a0065 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sun, 5 Oct 2025 18:04:41 +0200 Subject: [PATCH 54/77] quicksort example with JML scripts (partially) --- key.ui/examples/heap/quicksort/Quicksort.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/key.ui/examples/heap/quicksort/Quicksort.java b/key.ui/examples/heap/quicksort/Quicksort.java index 23d7bb6f0ff..75e85a0c7d0 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,21 @@ 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; + @ } */ + //@ assert (\forall int i; from<=i && i 0 ==> (\forall int x; from<=x && x<=to; array[x] > array[from-1]) \by { auto; } + //@ assert to < array.length-1 ==> (\forall int x; from<=x && x<=to; array[x] <= array[to+1]) \by { auto; } } } From 4579ddf083e76927db5c2ffb40268962482aa9e3 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sun, 5 Oct 2025 22:03:34 +0200 Subject: [PATCH 55/77] enabling the quicksort example with JML proof scripts! --- .../java/de/uka/ilkd/key/proof/NodeInfo.java | 5 ++-- .../uka/ilkd/key/scripts/BranchesCommand.java | 6 ++++ key.ui/examples/heap/quicksort/Quicksort.java | 28 +++++++++++++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) 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..73db8410e5c 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 @@ -26,6 +26,7 @@ import de.uka.ilkd.key.rule.Taclet; import de.uka.ilkd.key.rule.TacletApp; +import org.jspecify.annotations.Nullable; import org.key_project.logic.Name; import org.key_project.proof.LocationVariableTracker; import org.key_project.prover.rules.RuleApp; @@ -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/scripts/BranchesCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/BranchesCommand.java index 23696ecb000..9d6c186b7b9 100644 --- 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 @@ -67,6 +67,7 @@ public void execute(ScriptCommandAst arguments) throws ScriptException, Interrup goal = findGoalByName(root, args.branch); } state.setGoal(goal); + break; case "single": root = findNodeByNumber(proof, stack.peek()); TacletApp ta = (TacletApp) root.getAppliedRuleApp(); @@ -105,13 +106,18 @@ private void ensureSingleGoal() { 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); diff --git a/key.ui/examples/heap/quicksort/Quicksort.java b/key.ui/examples/heap/quicksort/Quicksort.java index 75e85a0c7d0..431eaf04726 100644 --- a/key.ui/examples/heap/quicksort/Quicksort.java +++ b/key.ui/examples/heap/quicksort/Quicksort.java @@ -73,9 +73,6 @@ private void sort(int[] array, int from, int to) { @ assert \dl_seqPerm(seq2, seq0) \by auto; @ auto; @ } */ - //@ assert (\forall int i; from<=i && i 0 ==> (\forall int x; from<=x && x<=to; array[x] > array[from-1]) \by { auto; } - //@ assert to < array.length-1 ==> (\forall int x; from<=x && x<=to; array[x] <= array[to+1]) \by { auto; } } } @@ -112,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++; } } @@ -119,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; } From dc725a0a26a7250a571c1a97d05f85cedd4ce942 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 6 Oct 2025 12:10:08 +0200 Subject: [PATCH 56/77] limiting the symbex only macro a bit added test cases --- .../macros/SymbolicExecutionOnlyMacro.java | 28 +++++++++++++++++++ .../uka/ilkd/key/scripts/JmlScriptTest.java | 5 ++-- .../key/scripts/TestProofScriptCommand.java | 8 +++--- .../uka/ilkd/key/scripts/cases/autoOnly.yml | 7 +++++ .../de/uka/ilkd/key/scripts/jml/AutoOnly.java | 19 +++++++++++++ .../uka/ilkd/key/scripts/jml/AutoOnly2.java | 18 ++++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/autoOnly.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java 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 index c47a4b958f1..1b876922f45 100644 --- 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 @@ -18,6 +18,8 @@ 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.prover.strategy.costbased.NumberRuleAppCost; import org.key_project.util.Streams; import org.key_project.util.java.StringUtil; @@ -25,6 +27,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; /** * This macro is very restritive in which rules are allowed to be applied for symbolic @@ -125,6 +129,7 @@ private static boolean isUpdatedModality(Term term) { 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); @@ -137,9 +142,32 @@ public Name name() { @Override public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { + if(!hasModality(goal)) { + return false; + } return isAdmittedRule(app) && super.isApprovedApp(app, pio, goal); } + 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/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java b/key.core/src/test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java index 75e91b84328..0f67cc7ecc3 100644 --- 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 @@ -38,7 +38,8 @@ public class JmlScriptTest { // Set this to a specific case to only run that case for debugging private static final String ONLY_CASE = null; - private static final boolean SAVE_PROOF = true; + // 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"); @@ -111,7 +112,7 @@ private static Parameters readParams(Path path) throws IOException { 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")); + 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")) 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 b4724c50264..1fdb1b9f088 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 @@ -56,7 +56,8 @@ public static List data() throws IOException, URISyntaxException { try { TestInstance instance = objectMapper.readValue(path.toFile(), TestInstance.class); - args.add(Arguments.of(instance)); + var name = instance.name == null ? path.getFileName().toString() : instance.name; + args.add(Arguments.of(instance, name)); } catch (Exception e) { System.out.println(path); e.printStackTrace(); @@ -67,10 +68,9 @@ 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"); Files.writeString(tmpKey, data.key()); 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/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..a3c11a11113 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java @@ -0,0 +1,18 @@ +//! 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"; + rule "close"; + } */ + } + +} From 080dfc69d6cb8d040f27e9378844467b9f8619fa Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 18 Jul 2024 15:31:16 +0200 Subject: [PATCH 57/77] introducing hole placeholders for terms and for formulas --- .../de/uka/ilkd/key/scripts/RuleCommand.java | 4 +- .../key/scripts/TermComparisonWithHoles.java | 201 ++++++++++++++++++ .../uka/ilkd/key/proof/rules/javaHeader.key | 7 + 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java 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 ceae3c429f1..3cfa634f7bf 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 @@ -371,8 +371,8 @@ private List filterList(Services services, Parameters p, for (TacletApp tacletApp : list) { if (tacletApp instanceof PosTacletApp pta) { JTerm term = (JTerm) pta.posInOccurrence().subTerm(); - boolean add = - p.on == null || RENAMING_TERM_PROPERTY.equalsModThisProperty(term, p.on); + boolean add = p.on == null + || TermComparisonWithHoles.compareModHoles(p.on, term); for (var entry : pta.instantiations().getInstantiationMap()) { final SchemaVariable sv = entry.key(); 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..4057ebbebd0 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java @@ -0,0 +1,201 @@ +package de.uka.ilkd.key.macros.scripts; + +import de.uka.ilkd.key.java.NameAbstractionTable; +import de.uka.ilkd.key.logic.Name; +import de.uka.ilkd.key.logic.Term; +import de.uka.ilkd.key.logic.op.*; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSLList; + +/** This is more a temporary hack for scripts ... */ +public class TermComparisonWithHoles { + + private static final Name HOLE_NAME = new Name("_"); + private static final Name HOLE_PREDICATE_NAME = new Name("__"); + + private static final NameAbstractionTable FAILED = new NameAbstractionTable(); + + public static boolean compareModHoles(Term t1, Term t2) { + if (t1 == t2) { + return true; + } + return unifyHelp(t1, t2, + ImmutableSLList.nil(), + ImmutableSLList.nil(), + null); + } + + + /** + * Compares two terms modulo bound renaming + * + * @param t0 the first term + * @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(Term t0, Term t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars, + NameAbstractionTable nat) { + + if (t0 == t1 && ownBoundVars.equals(cmpBoundVars)) { + return true; + } + + Operator op = t0.op(); + if(op instanceof SortDependingFunction) { + var sdop = (SortDependingFunction) op; + if(sdop.getKind().equals(HOLE_NAME)) { + return true; + } + } else if(op.name().equals(HOLE_PREDICATE_NAME)) { + return true; + } + + + final Operator op0 = t0.op(); + + if (op0 instanceof QuantifiableVariable) { + return handleQuantifiableVariable(t0, t1, ownBoundVars, + cmpBoundVars); + } + + final Operator op1 = t1.op(); + + if (!(op0 instanceof ProgramVariable) && op0 != op1) { + return false; + } + + if (t0.sort() != t1.sort() || t0.arity() != t1.arity()) { + return false; + } + + nat = handleJava(t0, t1, nat); + if (nat == FAILED) { + return false; + } + + return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, nat); + } + + private static boolean handleQuantifiableVariable(Term t0, Term t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars) { + if (!((t1.op() instanceof QuantifiableVariable) && compareBoundVariables( + (QuantifiableVariable) t0.op(), (QuantifiableVariable) t1.op(), + ownBoundVars, cmpBoundVars))) { + return false; + } + return true; + } + + private static NameAbstractionTable handleJava(Term t0, Term t1, + NameAbstractionTable nat) { + + if (!t0.javaBlock().isEmpty() || !t1.javaBlock().isEmpty()) { + nat = checkNat(nat); + if (!t0.javaBlock().equalsModRenaming(t1.javaBlock(), nat)) { + return FAILED; + } + } + + if (!(t0.op() instanceof SchemaVariable) + && t0.op() instanceof ProgramVariable) { + if (!(t1.op() instanceof ProgramVariable)) { + return FAILED; + } + nat = checkNat(nat); + if (!((ProgramVariable) t0.op()).equalsModRenaming( + (ProgramVariable) t1.op(), nat)) { + return FAILED; + } + } + + return nat; + } + + /** + * 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(Term t0, Term t1, + ImmutableList ownBoundVars, + ImmutableList cmpBoundVars, + NameAbstractionTable nat) { + + 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); + } + + boolean newConstraint = unifyHelp(t0.sub(i), t1.sub(i), + subOwnBoundVars, subCmpBoundVars, nat); + + if (!newConstraint) { + return false; + } + } + + return true; + } +} 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..8bfd655a6c7 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,11 @@ alpha alpha::cast(any); boolean alpha::exactInstance(any); boolean alpha::instance(any); + alpha alpha::_; // for matching in scripts +} + +\predicates { + __; // for matching scripts +} + } From 583e9af1964e6f4c879a19e64e394912ffa54cb0 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sun, 11 Aug 2024 16:04:07 +0200 Subject: [PATCH 58/77] introducing witness command --- .../uka/ilkd/key/scripts/WitnessCommand.java | 118 ++++++++++++++++++ ...de.uka.ilkd.key.scripts.ProofScriptCommand | 1 + 2 files changed, 119 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java 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..a919145c2d9 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java @@ -0,0 +1,118 @@ +/* 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.java.Services; +import de.uka.ilkd.key.logic.*; +import de.uka.ilkd.key.logic.op.Quantifier; +import de.uka.ilkd.key.logic.op.UpdateApplication; +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.proof.RuleAppIndex; +import de.uka.ilkd.key.rule.*; +import de.uka.ilkd.key.rule.inst.SVInstantiations; +import org.key_project.logic.Name; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSLList; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 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 tc = new TermComparisonWithHoles(services); + Pair match = goal.node().sequent().antecedent().asList().stream() + .filter(f -> tc.compareModHoles(params.formula, f.formula())) + .map(f -> new Pair<>(true, f)) + .findFirst().orElse( + goal.node().sequent().succedent().asList().stream() + .filter(f -> tc.compareModHoles(params.formula, f.formula())) + .map(f -> new Pair<>(false, f)) + .findFirst().orElse(null) + ); + + if (match == null) { + throw new ScriptException("Cannot 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); + } + + 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"), 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); + } + + public static class Parameters { + @Option(value = "as") + public String as; + @Option(value = "#2") + public Term formula; + } + +} 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 041a7133e63..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 @@ -19,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 From cbfcc76c829e002fc3a689a5596a045f643f2e5f Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 6 Oct 2025 15:02:05 +0200 Subject: [PATCH 59/77] adapting the cherry-picked commits --- .../key/scripts/TermComparisonWithHoles.java | 144 +++++++++++++----- .../uka/ilkd/key/scripts/WitnessCommand.java | 66 +++++--- .../uka/ilkd/key/proof/rules/javaHeader.key | 2 - .../key/scripts/TestProofScriptCommand.java | 24 ++- .../uka/ilkd/key/scripts/cases/witness1.yml | 6 + .../uka/ilkd/key/scripts/cases/witness2.yml | 8 + 6 files changed, 182 insertions(+), 68 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness1.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness2.yml 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 index 4057ebbebd0..7de4ebb06d0 100644 --- 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 @@ -1,21 +1,61 @@ -package de.uka.ilkd.key.macros.scripts; +package de.uka.ilkd.key.scripts; import de.uka.ilkd.key.java.NameAbstractionTable; -import de.uka.ilkd.key.logic.Name; -import de.uka.ilkd.key.logic.Term; +import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.op.*; +import org.jspecify.annotations.Nullable; +import org.key_project.logic.Name; +import org.key_project.logic.Property; +import org.key_project.logic.op.Operator; +import org.key_project.logic.op.QuantifiableVariable; +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; - -/** This is more a temporary hack for scripts ... */ +import org.key_project.util.collection.Pair; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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. + *

    + * The single instance of this property can be accessed through + * {@link TermComparisonWithHoles#INSTANCE}. + * + * @author Mattias Ulbrich + */ public class TermComparisonWithHoles { private static final Name HOLE_NAME = new Name("_"); private static final Name HOLE_PREDICATE_NAME = new Name("__"); private static final NameAbstractionTable FAILED = new NameAbstractionTable(); + private final JTerm referenceTerm; + + TermComparisonWithHoles(JTerm referenceTerm) { + this.referenceTerm = referenceTerm; + } + + public static boolean compare(JTerm referenceTerm, JTerm concreteTerm) { + TermComparisonWithHoles comparator = new TermComparisonWithHoles(referenceTerm); + return comparator.compareTo(concreteTerm); + } - public static boolean compareModHoles(Term t1, Term t2) { + public final boolean compareTo(JTerm t) { + if (referenceTerm == t) { + return true; + } + return unifyHelp(referenceTerm, t, + ImmutableSLList.nil(), + ImmutableSLList.nil(), + null); + } + + public static boolean compareModHoles(JTerm t1, JTerm t2) { if (t1 == t2) { return true; } @@ -29,14 +69,14 @@ public static boolean compareModHoles(Term t1, Term t2) { /** * Compares two terms modulo bound renaming * - * @param t0 the first term + * @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(Term t0, Term t1, + private static boolean unifyHelp(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars, NameAbstractionTable nat) { @@ -46,8 +86,7 @@ private static boolean unifyHelp(Term t0, Term t1, } Operator op = t0.op(); - if(op instanceof SortDependingFunction) { - var sdop = (SortDependingFunction) op; + if(op instanceof SortDependingFunction sdop) { if(sdop.getKind().equals(HOLE_NAME)) { return true; } @@ -73,15 +112,15 @@ private static boolean unifyHelp(Term t0, Term t1, return false; } - nat = handleJava(t0, t1, nat); - if (nat == FAILED) { - return false; - } +// nat = handleJava(t0, t1, nat); +// if (nat == FAILED) { +// return false; +// } return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, nat); } - private static boolean handleQuantifiableVariable(Term t0, Term t1, + private static boolean handleQuantifiableVariable(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars) { if (!((t1.op() instanceof QuantifiableVariable) && compareBoundVariables( @@ -92,30 +131,30 @@ private static boolean handleQuantifiableVariable(Term t0, Term t1, return true; } - private static NameAbstractionTable handleJava(Term t0, Term t1, - NameAbstractionTable nat) { - - if (!t0.javaBlock().isEmpty() || !t1.javaBlock().isEmpty()) { - nat = checkNat(nat); - if (!t0.javaBlock().equalsModRenaming(t1.javaBlock(), nat)) { - return FAILED; - } - } - - if (!(t0.op() instanceof SchemaVariable) - && t0.op() instanceof ProgramVariable) { - if (!(t1.op() instanceof ProgramVariable)) { - return FAILED; - } - nat = checkNat(nat); - if (!((ProgramVariable) t0.op()).equalsModRenaming( - (ProgramVariable) t1.op(), nat)) { - return FAILED; - } - } - - return nat; - } +// private static NameAbstractionTable handleJava(JTerm t0, JTerm t1, +// NameAbstractionTable nat) { +// +// if (!t0.javaBlock().isEmpty() || !t1.javaBlock().isEmpty()) { +// nat = checkNat(nat); +// if (!t0.javaBlock().equalsModRenaming(t1.javaBlock(), nat)) { +// return FAILED; +// } +// } +// +// if (!(t0.op() instanceof SchemaVariable) +// && t0.op() instanceof ProgramVariable) { +// if (!(t1.op() instanceof ProgramVariable)) { +// return FAILED; +// } +// nat = checkNat(nat); +// if (!((ProgramVariable) t0.op()).equalsModRenaming( +// (ProgramVariable) t1.op(), nat)) { +// return FAILED; +// } +// } +// +// return nat; +// } /** * compare two quantifiable variables if they are equal modulo renaming @@ -165,7 +204,7 @@ private static NameAbstractionTable checkNat(NameAbstractionTable nat) { return nat; } - private static boolean descendRecursively(Term t0, Term t1, + private static boolean descendRecursively(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars, NameAbstractionTable nat) { @@ -198,4 +237,29 @@ private static boolean descendRecursively(Term t0, Term t1, return true; } + + public List> findMatchesInSequent(Sequent sequent) { + List> matches = new ArrayList<>(); + for (SequentFormula sf : sequent.antecedent()) { + if (compareTo((JTerm) sf.formula())) { + matches.add(new Pair<>(true, sf)); + } + } + for (SequentFormula sf : sequent.succedent()) { + if (compareTo((JTerm) sf.formula())) { + matches.add(new Pair<>(false, sf)); + } + } + return matches; + } + + + public @Nullable Pair findUniqueMatchInSequent(Sequent sequent) { + List> matches = findMatchesInSequent(sequent); + if (matches.size() != 1) { + return null; + } else { + return matches.getFirst(); + } + } } 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 index a919145c2d9..8e2031a3277 100644 --- 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 @@ -4,20 +4,22 @@ package de.uka.ilkd.key.scripts; -import de.uka.ilkd.key.control.AbstractUserInterfaceControl; 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.logic.op.UpdateApplication; 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.proof.RuleAppIndex; import de.uka.ilkd.key.rule.*; -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 de.uka.ilkd.key.scripts.meta.Option; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.key_project.logic.Name; -import org.key_project.util.collection.ImmutableList; -import org.key_project.util.collection.ImmutableSLList; +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 java.util.NoSuchElementException; import java.util.Set; @@ -57,19 +59,12 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc Goal goal = state.getFirstOpenAutomaticGoal(); Services services = state.getProof().getServices(); - TermComparisonWithHoles tc = new TermComparisonWithHoles(services); - Pair match = goal.node().sequent().antecedent().asList().stream() - .filter(f -> tc.compareModHoles(params.formula, f.formula())) - .map(f -> new Pair<>(true, f)) - .findFirst().orElse( - goal.node().sequent().succedent().asList().stream() - .filter(f -> tc.compareModHoles(params.formula, f.formula())) - .map(f -> new Pair<>(false, f)) - .findFirst().orElse(null) - ); + TermComparisonWithHoles comp = new TermComparisonWithHoles(params.formula); + // First component: true for antecedent, false for succedent + Pair match = comp.findUniqueMatchInSequent(goal.node().sequent()); if (match == null) { - throw new ScriptException("Cannot match the formula argument"); + throw new ScriptException("Cannot unique match the formula argument"); } Operator op = match.second.formula().op(); @@ -82,6 +77,15 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc 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); @@ -93,7 +97,7 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc 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"), 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); @@ -108,11 +112,27 @@ private static SchemaVariable getSV(Set schemaVars, String name) 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 String as; - @Option(value = "#2") - public Term formula; + public @MonotonicNonNull String as; + + @Documentation("The formula containing the quantifier for which a witness should be provided. Placeholders are allowed.") + @Argument + public @MonotonicNonNull JTerm formula; } } 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 8bfd655a6c7..30620d309ba 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 @@ -34,5 +34,3 @@ \predicates { __; // for matching scripts } - -} 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 1fdb1b9f088..d9e8d2203b8 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 @@ -28,6 +28,9 @@ 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 recoder.util.Debug; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,15 +39,27 @@ * see {@link MasterHandlerTest} from where I copied quite a bit. */ public class TestProofScriptCommand { + + private static final String ONLY_CASE = "witness2.yml"; + 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(); + + if(ONLY_CASE != null) { + folder = folder.resolve(ONLY_CASE); + } + try (var walker = Files.walk(folder)) { List files = walker.filter(it -> it.getFileName().toString().endsWith(".yml")).toList(); @@ -56,7 +71,9 @@ public static List data() throws IOException, URISyntaxException { try { TestInstance instance = objectMapper.readValue(path.toFile(), TestInstance.class); - var name = instance.name == null ? path.getFileName().toString() : instance.name; + 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); @@ -72,6 +89,7 @@ public static List data() throws IOException, URISyntaxException { @MethodSource("data") 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); 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..6a6d3b9f5cb --- /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; (__ | __)) 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/witness2.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness2.yml new file mode 100644 index 00000000000..9dada861b16 --- /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; (__ | __)) as="x1"; +exception: "Name already used as function or predicate: x1" From dbd0d1de4c5300d3bc675fea8a974b5b0fbdc009 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Mon, 6 Oct 2025 16:03:46 +0200 Subject: [PATCH 60/77] allowing switching off rules in auto from 53286f761f547ae662e6275fb0693b92f2c502ba --- .../key/scripts/AdditionalRulesStrategy.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) 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 index bb0609b0653..39240fdfc1f 100644 --- 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 @@ -12,6 +12,7 @@ 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 java.util.ArrayList; @@ -26,9 +27,9 @@ class AdditionalRulesStrategy extends FilterStrategy { private static final Map TRANSLATIONS = Map.of("high", "-50", "medium", "1000", "low", "10000"); - private static final int DEFAULT_PRIORITY = 1000; + private static final RuleAppCost DEFAULT_PRIORITY = NumberRuleAppCost.create(1000); - private final List> additionalRules; + private final List> additionalRules; private final boolean exclusive; public AdditionalRulesStrategy(Strategy delegate, String additionalRules, boolean exclusive) { @@ -37,16 +38,19 @@ public AdditionalRulesStrategy(Strategy delegate, String additionalRules, boolea this.exclusive = exclusive; } - private List> parseAddRules(String additionalRules) { - List> result = new ArrayList<>(); + private List> parseAddRules(String additionalRules) { + List> result = new ArrayList<>(); for (String entry : additionalRules.trim().split(" *, *")) { String[] parts = entry.split(" *= *", 2); - int prio; + RuleAppCost prio; if (parts.length == 2) { String prioStr = parts[1]; + if(prioStr.equals("off")) { + prio = TopRuleAppCost.INSTANCE; + } prioStr = TRANSLATIONS.getOrDefault(prioStr, prioStr); try { - prio = Integer.parseInt(prioStr); + prio = NumberRuleAppCost.create(Integer.parseInt(prioStr)); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid value for additional rule: " + parts[1]); } @@ -88,9 +92,9 @@ public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { private @Nullable RuleAppCost computeLocalCost(Rule rule) { String name = rule.name().toString(); - Optional cost = lookup(name); + Optional cost = lookup(name); if(cost.isPresent()) { - return NumberRuleAppCost.create(cost.get()); + return cost.get(); } if (rule instanceof Taclet taclet) { @@ -98,7 +102,7 @@ public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { String rname = rs.name().toString(); cost = lookup(rname); if(cost.isPresent()) { - return NumberRuleAppCost.create(cost.get()); + return cost.get(); } } } @@ -106,7 +110,7 @@ public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { return null; } - private Optional lookup(String name) { + private Optional lookup(String name) { return additionalRules.stream() .filter(nameAndPrio -> name.matches(nameAndPrio.first)) .findFirst() From 5d4196aeee075f13df22eecc39ce15ba40e633e2 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 9 Oct 2025 16:31:43 +0200 Subject: [PATCH 61/77] a more robust term-with-holes implementation --- .../de/uka/ilkd/key/scripts/EngineState.java | 2 + .../key/scripts/TermComparisonWithHoles.java | 13 ++- .../uka/ilkd/key/scripts/TermWithHoles.java | 105 ++++++++++++++++++ .../uka/ilkd/key/scripts/WitnessCommand.java | 2 +- .../ilkd/key/scripts/meta/ValueInjector.java | 20 ++-- .../uka/ilkd/key/proof/rules/javaHeader.key | 4 +- .../key/scripts/TestProofScriptCommand.java | 3 +- .../de/uka/ilkd/key/scripts/cases/holes1.yml | 6 + .../ilkd/key/scripts/cases/instantiate1.yml | 6 + .../uka/ilkd/key/scripts/cases/witness1.yml | 4 +- .../uka/ilkd/key/scripts/cases/witness2.yml | 2 +- .../util/collection/ImmutableSet.java | 9 ++ 12 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/instantiate1.yml 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 c20b6e8ab8e..1d79e0f748b 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 @@ -86,6 +86,8 @@ private ValueInjector createDefaultValueInjector() { 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); + v.addConverter(TermWithHoles.class, ProofScriptExpressionContext.class, + (ctx) -> TermWithHoles.fromParserContext(this, ctx)); addContextTranslator(v, String.class); addContextTranslator(v, JTerm.class); 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 index 7de4ebb06d0..c9aa98bffa2 100644 --- 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 @@ -17,6 +17,8 @@ import java.util.ArrayList; import java.util.List; +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 @@ -30,9 +32,6 @@ */ public class TermComparisonWithHoles { - private static final Name HOLE_NAME = new Name("_"); - private static final Name HOLE_PREDICATE_NAME = new Name("__"); - private static final NameAbstractionTable FAILED = new NameAbstractionTable(); private final JTerm referenceTerm; @@ -40,6 +39,10 @@ public class TermComparisonWithHoles { this.referenceTerm = referenceTerm; } + TermComparisonWithHoles(TermWithHoles twh) { + this.referenceTerm = twh.term(); + } + public static boolean compare(JTerm referenceTerm, JTerm concreteTerm) { TermComparisonWithHoles comparator = new TermComparisonWithHoles(referenceTerm); return comparator.compareTo(concreteTerm); @@ -87,10 +90,10 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, Operator op = t0.op(); if(op instanceof SortDependingFunction sdop) { - if(sdop.getKind().equals(HOLE_NAME)) { + if(sdop.getKind().equals(HOLE_SORT_DEP_NAME)) { return true; } - } else if(op.name().equals(HOLE_PREDICATE_NAME)) { + } else if(op.name().equals(HOLE_PREDICATE_NAME) || op.name().equals(HOLE_NAME)) { return true; } 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..3c1ac754492 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.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.scripts; + +import de.uka.ilkd.key.control.AbstractProofControl; +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.builder.ExpressionBuilder; +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.prover.impl.ApplyStrategy; +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 de.uka.ilkd.key.util.parsing.BuildingIssue; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +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.engine.ProverCore; +import org.key_project.prover.strategy.RuleApplicationManager; +import org.key_project.util.collection.ImmutableArray; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.collection.ImmutableSLList; +import org.key_project.util.collection.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static de.uka.ilkd.key.strategy.StrategyProperties.*; + +@NullMarked +public record TermWithHoles(JTerm term) { + + public static final Name HOLE_NAME = new Name("_"); + public static final Name HOLE_PREDICATE_NAME = new Name("__"); + public static final Name HOLE_SORT_DEP_NAME = new Name("___"); + + private static final Logger LOGGER = LoggerFactory.getLogger(TermWithHoles.class); + + public boolean matches(JTerm other) { + return TermComparisonWithHoles.compareModHoles(term, other); + } + + 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 fromParserContext(EngineState state, KeYParser.ProofScriptExpressionContext ctx) throws ScriptException { + 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)); + 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/WitnessCommand.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/WitnessCommand.java index 8e2031a3277..4b4484c2a9f 100644 --- 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 @@ -132,7 +132,7 @@ public static class Parameters { @Documentation("The formula containing the quantifier for which a witness should be provided. Placeholders are allowed.") @Argument - public @MonotonicNonNull JTerm formula; + public @MonotonicNonNull TermWithHoles formula; } } 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 d4a33e16384..fbe40fbac3d 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 @@ -6,6 +6,7 @@ import java.util.*; import java.util.stream.Collectors; +import de.uka.ilkd.key.nparser.KeYParser; import de.uka.ilkd.key.scripts.ProofScriptCommand; import de.uka.ilkd.key.scripts.ScriptCommandAst; @@ -287,16 +288,12 @@ 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), + val, val.getClass().getSimpleName(), targetType.getSimpleName()), e); } } @@ -306,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) { @@ -347,11 +340,16 @@ public void addConverter(Converter conv) { * converter is known. */ @SuppressWarnings("unchecked") - public @Nullable Converter getConverter(Class ret, Class 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/resources/de/uka/ilkd/key/proof/rules/javaHeader.key b/key.core/src/main/resources/de/uka/ilkd/key/proof/rules/javaHeader.key index 30620d309ba..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,9 +28,9 @@ alpha alpha::cast(any); boolean alpha::exactInstance(any); boolean alpha::instance(any); - alpha alpha::_; // for matching in scripts + // alpha alpha::_; // for matching in scripts } \predicates { - __; // for matching scripts + // __; // for matching scripts } 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 d9e8d2203b8..1e80bf7ac56 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 @@ -40,7 +40,7 @@ */ public class TestProofScriptCommand { - private static final String ONLY_CASE = "witness2.yml"; + private static final String ONLY_CASE = null; private static final Logger LOGGER = LoggerFactory.getLogger(TestProofScriptCommand.class); public record TestInstance( @@ -103,6 +103,7 @@ void testProofScript(TestInstance data, String name) throws Exception { try { 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. 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..c23c2c93cc6 --- /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/instantiate1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/instantiate1.yml new file mode 100644 index 00000000000..30f9b28acd0 --- /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/witness1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/witness1.yml index 6a6d3b9f5cb..0edb8b5ccf6 100644 --- 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 @@ -1,6 +1,6 @@ key: | \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } script: | - witness (\forall int x; (__ | __)) as="y"; + witness (\forall int x; (x>0 | x<2)) as="y"; goals: - - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file + - ==> \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 index 9dada861b16..69e2d245925 100644 --- 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 @@ -4,5 +4,5 @@ key: | \programVariables { int x3; } \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < x1) } script: | - witness (\forall int x; (__ | __)) as="x1"; + witness (\forall int x; (x > 0 | x < x1)) as="x1"; exception: "Name already used as function or predicate: x1" 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..2d633534012 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 @@ -9,6 +9,7 @@ import java.util.stream.Collector.Characteristics; import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; /** @@ -43,6 +44,14 @@ public interface ImmutableSet return DefaultImmutableSet.nil(); } + 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} */ From 453082f69933ac508235dd3fc771c233fb8902e5 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 10 Oct 2025 13:49:47 +0200 Subject: [PATCH 62/77] improved treatment of terms with holes --- .../de/uka/ilkd/key/scripts/EngineState.java | 5 +++ .../de/uka/ilkd/key/scripts/RuleCommand.java | 6 +-- .../key/scripts/TermComparisonWithHoles.java | 28 ++---------- .../uka/ilkd/key/scripts/TermWithHoles.java | 43 +++++++++++++++++-- .../uka/ilkd/key/scripts/WitnessCommand.java | 2 +- .../de/uka/ilkd/key/scripts/cases/holes1.yml | 2 +- 6 files changed, 53 insertions(+), 33 deletions(-) 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 1d79e0f748b..bf5556b4214 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 @@ -81,6 +81,9 @@ public EngineState(Proof proof, ProofScriptEngine engine) { this.engine = engine; } + // Problem is: This is all KeY specific and there should be different converters + // for JML. But how to separate this? Probably do this externally after PSE generation. + // via some "enrichValueInjector" method in a different class ... private ValueInjector createDefaultValueInjector() { var v = ValueInjector.createDefault(); v.addConverter(JTerm.class, String.class, (str) -> this.toTerm(str, null)); @@ -88,6 +91,8 @@ private ValueInjector createDefaultValueInjector() { v.addConverter(Sort.class, String.class, this::toSort); v.addConverter(TermWithHoles.class, ProofScriptExpressionContext.class, (ctx) -> TermWithHoles.fromParserContext(this, ctx)); + v.addConverter(TermWithHoles.class, String.class, + (str) -> TermWithHoles.fromString(this, str)); addContextTranslator(v, String.class); addContextTranslator(v, JTerm.class); 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 3cfa634f7bf..9372eba5ef6 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 @@ -368,11 +368,11 @@ private static String formatTermString(String str) { 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) { if (tacletApp instanceof PosTacletApp pta) { JTerm term = (JTerm) pta.posInOccurrence().subTerm(); - boolean add = p.on == null - || TermComparisonWithHoles.compareModHoles(p.on, term); + boolean add = p.on == null || p.on.matches(term); for (var entry : pta.instantiations().getInstantiationMap()) { final SchemaVariable sv = entry.key(); @@ -410,7 +410,7 @@ public static class Parameters { @Option(value = "on") @Documentation("Term on which the rule should be applied to (matching the 'find' clause of the rule). " + "This may contain placeholders.") - public @Nullable JTerm on; + public @Nullable TermWithHoles on; @Option(value = "formula") @Documentation("Top-level formula in which the term appears. This may contain placeholders.") 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 index c9aa98bffa2..ce39ec9135d 100644 --- 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 @@ -4,8 +4,6 @@ import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.op.*; import org.jspecify.annotations.Nullable; -import org.key_project.logic.Name; -import org.key_project.logic.Property; import org.key_project.logic.op.Operator; import org.key_project.logic.op.QuantifiableVariable; import org.key_project.prover.sequent.Sequent; @@ -39,16 +37,7 @@ public class TermComparisonWithHoles { this.referenceTerm = referenceTerm; } - TermComparisonWithHoles(TermWithHoles twh) { - this.referenceTerm = twh.term(); - } - - public static boolean compare(JTerm referenceTerm, JTerm concreteTerm) { - TermComparisonWithHoles comparator = new TermComparisonWithHoles(referenceTerm); - return comparator.compareTo(concreteTerm); - } - - public final boolean compareTo(JTerm t) { + public final boolean matches(JTerm t) { if (referenceTerm == t) { return true; } @@ -58,17 +47,6 @@ public final boolean compareTo(JTerm t) { null); } - public static boolean compareModHoles(JTerm t1, JTerm t2) { - if (t1 == t2) { - return true; - } - return unifyHelp(t1, t2, - ImmutableSLList.nil(), - ImmutableSLList.nil(), - null); - } - - /** * Compares two terms modulo bound renaming * @@ -244,12 +222,12 @@ private static boolean descendRecursively(JTerm t0, JTerm t1, public List> findMatchesInSequent(Sequent sequent) { List> matches = new ArrayList<>(); for (SequentFormula sf : sequent.antecedent()) { - if (compareTo((JTerm) sf.formula())) { + if (matches((JTerm) sf.formula())) { matches.add(new Pair<>(true, sf)); } } for (SequentFormula sf : sequent.succedent()) { - if (compareTo((JTerm) sf.formula())) { + if (matches((JTerm) sf.formula())) { matches.add(new Pair<>(false, sf)); } } 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 index 3c1ac754492..c116a50c0c2 100644 --- 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 @@ -12,6 +12,8 @@ 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.KeyAst; +import de.uka.ilkd.key.nparser.ParsingFacade; import de.uka.ilkd.key.nparser.builder.ExpressionBuilder; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; @@ -22,6 +24,8 @@ import de.uka.ilkd.key.strategy.Strategy; import de.uka.ilkd.key.strategy.StrategyProperties; import de.uka.ilkd.key.util.parsing.BuildingIssue; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.ParserRuleContext; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -43,8 +47,33 @@ import static de.uka.ilkd.key.strategy.StrategyProperties.*; +/* + +y < (x+0) + y + (x+0), +a -> y < (x+0) + y + (x+0) + + +rule plus_zero on: ????? + +---> a -> y < (x+0) + y + FOCUS(x+0) + +---> _ -> (... _ + (_+0) ...) + +---> _ -> _find(_ + _focus(_ + 0)) + +---> ? -> ?find(? + ?focus(? + 0)) + +---> ?_ -> ?find(?_ + ?focus(_ + 0)) + */ + @NullMarked -public record TermWithHoles(JTerm term) { +public class TermWithHoles { + + private final JTerm term; + + public TermWithHoles(JTerm term) { + this.term = term; + } public static final Name HOLE_NAME = new Name("_"); public static final Name HOLE_PREDICATE_NAME = new Name("__"); @@ -53,7 +82,11 @@ public record TermWithHoles(JTerm term) { private static final Logger LOGGER = LoggerFactory.getLogger(TermWithHoles.class); public boolean matches(JTerm other) { - return TermComparisonWithHoles.compareModHoles(term, other); + return getMatcher().matches(other); + } + + public TermComparisonWithHoles getMatcher() { + return new TermComparisonWithHoles(term); } private static class NothingSort extends AbstractSort { @@ -75,8 +108,12 @@ public boolean extendsTrans(Sort s) { } } + public static TermWithHoles fromString(EngineState engineState, String str) { + KeyAst.Term term = ParsingFacade.parseExpression(CharStreams.fromString(str)); + return fromParserContext(engineState, term.ctx); + } - public static TermWithHoles fromParserContext(EngineState state, KeYParser.ProofScriptExpressionContext ctx) throws ScriptException { + public static TermWithHoles fromParserContext(EngineState state, ParserRuleContext ctx) { var expressionBuilder = new ExpressionBuilder(state.getProof().getServices(), enrichState(state)); expressionBuilder.setAbbrevMap(state.getAbbreviations()); 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 index 4b4484c2a9f..84b22f7382c 100644 --- 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 @@ -59,7 +59,7 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc Goal goal = state.getFirstOpenAutomaticGoal(); Services services = state.getProof().getServices(); - TermComparisonWithHoles comp = new TermComparisonWithHoles(params.formula); + TermComparisonWithHoles comp = params.formula.getMatcher(); // First component: true for antecedent, false for succedent Pair match = comp.findUniqueMatchInSequent(goal.node().sequent()); 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 index c23c2c93cc6..9c8a4edb471 100644 --- 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 @@ -1,6 +1,6 @@ key: | \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } script: | - witness (\forall int x; (__ | x < _)) as="y"; + witness (\forall int x; (_ | x < _)) as="y"; goals: - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file From 3f670483c59121babd1bec297098f7ec50809cd6 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 10 Oct 2025 15:52:13 +0200 Subject: [PATCH 63/77] introduce match identifiers with leading '?' --- key.core/src/main/antlr4/KeYLexer.g4 | 1 + key.core/src/main/antlr4/KeYParser.g4 | 4 +++- .../de/uka/ilkd/key/nparser/builder/DefaultBuilder.java | 2 +- .../main/java/de/uka/ilkd/key/scripts/TermWithHoles.java | 6 +++--- .../test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/key.core/src/main/antlr4/KeYLexer.g4 b/key.core/src/main/antlr4/KeYLexer.g4 index 0a692adf53c..7b632d198cd 100644 --- a/key.core/src/main/antlr4/KeYLexer.g4 +++ b/key.core/src/main/antlr4/KeYLexer.g4 @@ -391,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/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/scripts/TermWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermWithHoles.java index c116a50c0c2..c8645755c7c 100644 --- 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 @@ -75,9 +75,9 @@ public TermWithHoles(JTerm term) { this.term = term; } - public static final Name HOLE_NAME = new Name("_"); - public static final Name HOLE_PREDICATE_NAME = new Name("__"); - public static final Name HOLE_SORT_DEP_NAME = new Name("___"); + 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("?"); private static final Logger LOGGER = LoggerFactory.getLogger(TermWithHoles.class); 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 index 9c8a4edb471..d1b61bb7092 100644 --- 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 @@ -1,6 +1,6 @@ key: | \problem { ==> \forall int x; x=x, \forall int x; (x > 0 | x < 2) } script: | - witness (\forall int x; (_ | x < _)) as="y"; + witness (\forall int x; (? | x < ?)) as="y"; goals: - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file From e448592843ae21860b4a61b35c2ee65baac0b2c1 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 18 Jul 2024 16:44:04 +0200 Subject: [PATCH 64/77] introducing sort::FOCUS for usage in scripts --- .../de/uka/ilkd/key/scripts/RuleCommand.java | 3 +- .../key/scripts/TermComparisonWithHoles.java | 66 +++++++++++++++---- .../uka/ilkd/key/scripts/TermWithHoles.java | 14 +++- .../uka/ilkd/key/scripts/WitnessCommand.java | 2 +- .../de/uka/ilkd/key/scripts/cases/focus1.yml | 7 ++ .../java/org/key_project/logic/PosInTerm.java | 7 ++ 6 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus1.yml 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 9372eba5ef6..88b328815a0 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 @@ -371,8 +371,7 @@ private List filterList(Services services, Parameters p, TermComparisonWithHoles matcher = p.on == null ? null : p.on.getMatcher(); for (TacletApp tacletApp : list) { if (tacletApp instanceof PosTacletApp pta) { - JTerm term = (JTerm) pta.posInOccurrence().subTerm(); - boolean add = p.on == null || p.on.matches(term); + boolean add = matcher == null || matcher.matches(pta.posInOccurrence()); for (var entry : pta.instantiations().getInstantiationMap()) { final SchemaVariable sv = entry.key(); 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 index ce39ec9135d..9649514cb69 100644 --- 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 @@ -2,10 +2,14 @@ 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.jspecify.annotations.Nullable; +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; @@ -22,9 +26,6 @@ * 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. - *

    - * The single instance of this property can be accessed through - * {@link TermComparisonWithHoles#INSTANCE}. * * @author Mattias Ulbrich */ @@ -37,16 +38,56 @@ public class TermComparisonWithHoles { this.referenceTerm = referenceTerm; } - public final boolean matches(JTerm t) { - if (referenceTerm == t) { + public final boolean matches(PosInOccurrence pio) { + JTerm term = (JTerm) pio.subTerm(); + if (term.equalsModProperty(referenceTerm, RenamingTermProperty.RENAMING_TERM_PROPERTY)) { return true; } - return unifyHelp(referenceTerm, t, + + PosInTerm focus = findFocus(referenceTerm); + if(focus != null) { + for(int i = focus.depth() -1; i >= 0; i--) { + int focusIdx = focus.getIndexAt(i); + int termIdx = pio.posInTerm().getIndexAt(pio.depth() - 1); + if(focusIdx != termIdx) { + // the focus is not at the same position as the current term + return false; + } + pio = pio.up(); + } + term = (JTerm) pio.subTerm(); + } + + return unifyHelp(referenceTerm, term, ImmutableSLList.nil(), ImmutableSLList.nil(), null); + + } + + 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(); + } + } + for (int i = 0; i < pattern.arity(); i++) { + Term sub = pattern.sub(i); + PosInTerm subFocus = findFocus(sub); + if(subFocus != null) { + return PosInTerm.of((char)i, subFocus); + } + } + return null; } + /** * Compares two terms modulo bound renaming * @@ -73,6 +114,9 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, } } else if(op.name().equals(HOLE_PREDICATE_NAME) || op.name().equals(HOLE_NAME)) { return true; + } else if(op.name().equals(FOCUS_NAME)) { + // ignore the "focus" annotation + return unifyHelp(t0.sub(0), t1, ownBoundVars, cmpBoundVars, nat); } @@ -219,15 +263,15 @@ private static boolean descendRecursively(JTerm t0, JTerm t1, return true; } - public List> findMatchesInSequent(Sequent sequent) { + public List> findTopLevelMatchesInSequent(Sequent sequent) { List> matches = new ArrayList<>(); for (SequentFormula sf : sequent.antecedent()) { - if (matches((JTerm) sf.formula())) { + if (matchesToplevel(sf)) { matches.add(new Pair<>(true, sf)); } } for (SequentFormula sf : sequent.succedent()) { - if (matches((JTerm) sf.formula())) { + if (matchesToplevel(sf)) { matches.add(new Pair<>(false, sf)); } } @@ -235,8 +279,8 @@ public List> findMatchesInSequent(Sequent sequent) } - public @Nullable Pair findUniqueMatchInSequent(Sequent sequent) { - List> matches = findMatchesInSequent(sequent); + public @Nullable Pair findUniqueToplevelMatchInSequent(Sequent sequent) { + List> matches = findTopLevelMatchesInSequent(sequent); if (matches.size() != 1) { return null; } else { 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 index c8645755c7c..4062b3428f2 100644 --- 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 @@ -33,6 +33,8 @@ import org.key_project.logic.sort.AbstractSort; import org.key_project.logic.sort.Sort; import org.key_project.prover.engine.ProverCore; +import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.sequent.SequentFormula; import org.key_project.prover.strategy.RuleApplicationManager; import org.key_project.util.collection.ImmutableArray; import org.key_project.util.collection.ImmutableList; @@ -70,19 +72,24 @@ public class TermWithHoles { private final JTerm term; - public TermWithHoles(JTerm term) { this.term = 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"); private static final Logger LOGGER = LoggerFactory.getLogger(TermWithHoles.class); - public boolean matches(JTerm other) { - return getMatcher().matches(other); + public boolean matches(PosInOccurrence posInOccurrence) { + return getMatcher().matches(posInOccurrence); + } + + public boolean matchesToplevel(SequentFormula sf) { + return getMatcher().matchesToplevel(sf); } public TermComparisonWithHoles getMatcher() { @@ -133,6 +140,7 @@ private static NamespaceSet enrichState(EngineState state) { 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)); GenericSort g = new GenericSort(new Name("G")); ns.functions().addSafely(SortDependingFunction.createFirstInstance(g, HOLE_SORT_DEP_NAME, g, new Sort[0], false)); 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 index 84b22f7382c..c5d7f94721e 100644 --- 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 @@ -62,7 +62,7 @@ public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedExc TermComparisonWithHoles comp = params.formula.getMatcher(); // First component: true for antecedent, false for succedent - Pair match = comp.findUniqueMatchInSequent(goal.node().sequent()); + Pair match = comp.findUniqueToplevelMatchInSequent(goal.node().sequent()); if (match == null) { throw new ScriptException("Cannot unique match the formula argument"); } 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.ncore/src/main/java/org/key_project/logic/PosInTerm.java b/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java index b705e697bf2..b9b3c59ba1a 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 static PosInTerm of(char index, PosInTerm subPos) { + var newPositions = new char[subPos.size + 1]; + System.arraycopy(subPos.positions, 0, newPositions, 1, subPos.size); + newPositions[0] = index; + return new PosInTerm(newPositions, (char) (subPos.size + 1), true); + } + /// returns the instance representing the top level position /// /// @return the top level position From 86472e6febdc7c12811eb16c3b92d4999c99c5b2 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Thu, 18 Jul 2024 17:04:14 +0200 Subject: [PATCH 65/77] introducing sort::ELLIP for ellipsis search patterns --- .../ilkd/key/scripts/TermComparisonWithHoles.java | 14 ++++++++++++++ .../de/uka/ilkd/key/scripts/TermWithHoles.java | 2 ++ .../de/uka/ilkd/key/scripts/cases/find1.yml | 7 +++++++ 3 files changed, 23 insertions(+) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find1.yml 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 index 9649514cb69..77c88905c79 100644 --- 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 @@ -17,7 +17,9 @@ import org.key_project.util.collection.Pair; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static de.uka.ilkd.key.scripts.TermWithHoles.*; @@ -117,6 +119,13 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, } else if(op.name().equals(FOCUS_NAME)) { // ignore the "focus" annotation return unifyHelp(t0.sub(0), t1, ownBoundVars, cmpBoundVars, nat); + } else if(op.name().equals(ELLIPSIS_NAME)) { + // return true if it hits one subterm ... + Set deepAllSubs = new HashSet<>(); + computeSubterms(t1, deepAllSubs); + var lookfor = t0.sub(0); + var finalNat = nat; + return deepAllSubs.stream().anyMatch(t -> unifyHelp(lookfor, t, ownBoundVars, cmpBoundVars, finalNat)); } @@ -145,6 +154,11 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, nat); } + private static void computeSubterms(JTerm t, Set deepAllSubs) { + deepAllSubs.add(t); + t.subs().stream().forEach(sub -> computeSubterms(sub, deepAllSubs)); + } + private static boolean handleQuantifiableVariable(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars) { 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 index 4062b3428f2..70bb08d5e47 100644 --- 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 @@ -81,6 +81,7 @@ public TermWithHoles(JTerm term) { 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); @@ -141,6 +142,7 @@ private static NamespaceSet enrichState(EngineState state) { 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)); 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 From 07f8cda82dcecdc8be5d35025f9257f6071d03e1 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 10 Oct 2025 22:18:54 +0200 Subject: [PATCH 66/77] improving sophisticated term matching there are failing cases ... --- .../ilkd/key/scripts/ScriptCommandAst.java | 4 +- .../key/scripts/TermComparisonWithHoles.java | 5 +- .../java/de/uka/ilkd/key/util/ANTLRUtil.java | 96 +++++++++++++++++++ .../key/scripts/TestProofScriptCommand.java | 10 +- .../de/uka/ilkd/key/scripts/cases/find2.yml | 7 ++ .../de/uka/ilkd/key/scripts/cases/focus2.yml | 8 ++ .../ilkd/key/scripts/cases/focusOfFind.yml | 7 ++ .../de/uka/ilkd/key/scripts/cases/holes1.yml | 2 +- .../ilkd/key/scripts/cases/instantiate1.yml | 2 +- .../uka/ilkd/key/scripts/cases/witness1.yml | 2 +- 10 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/util/ANTLRUtil.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find2.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus2.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focusOfFind.yml 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..4fe7bfc0f01 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 @@ -11,6 +11,7 @@ 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; import org.jspecify.annotations.Nullable; @@ -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/TermComparisonWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java index 77c88905c79..a498b4cd685 100644 --- 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 @@ -33,7 +33,6 @@ */ public class TermComparisonWithHoles { - private static final NameAbstractionTable FAILED = new NameAbstractionTable(); private final JTerm referenceTerm; TermComparisonWithHoles(JTerm referenceTerm) { @@ -49,6 +48,10 @@ public final boolean matches(PosInOccurrence pio) { PosInTerm focus = findFocus(referenceTerm); if(focus != null) { for(int i = focus.depth() -1; i >= 0; i--) { + if(pio.isTopLevel()) { + // focus is deeper than the current term + return false; + } int focusIdx = focus.getIndexAt(i); int termIdx = pio.posInTerm().getIndexAt(pio.depth() - 1); if(focusIdx != termIdx) { 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..c4be36280ae --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/util/ANTLRUtil.java @@ -0,0 +1,96 @@ +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; + } +} \ No newline at end of file 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 1e80bf7ac56..5413957143d 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 @@ -15,6 +15,8 @@ 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.pp.LogicPrinter; +import de.uka.ilkd.key.pp.NotationInfo; import de.uka.ilkd.key.proof.Goal; import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.smt.newsmt2.MasterHandlerTest; @@ -30,7 +32,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import recoder.util.Debug; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -124,7 +125,7 @@ void testProofScript(TestInstance data, String name) throws Exception { Assertions.assertEquals(expected, goals.size()); for (String expectedGoal : data.goals()) { - assertThat(goals.head().toString().trim()).isEqualTo(expectedGoal); + assertThat(normaliseSpace(goals.head().toString().trim())).isEqualTo(expectedGoal); goals = goals.tail(); } @@ -135,4 +136,9 @@ void testProofScript(TestInstance data, String name) throws Exception { } } + // For some layout reasons the toString may add linebreaks and spaces + private static String normaliseSpace(String str) { + return str.replaceAll("\\s+", " "); + } + } 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..3af65ac6ad8 --- /dev/null +++ b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/find2.yml @@ -0,0 +1,7 @@ +key: | + \programVariables { int a; } + \problem { ==> 1 + 0 = 1 & 2 + 0 = 2 & 3 + 0 = 3 } +script: | + rule add_zero_right on:(?find(2)); +goals: + - ==> 1 + 0 = 1 & 0 + 2 + 5 = 7 \ 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/holes1.yml b/key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes1.yml index d1b61bb7092..c73fcf31c8d 100644 --- 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 @@ -3,4 +3,4 @@ key: | script: | witness (\forall int x; (? | x < ?)) as="y"; goals: - - ==> \forall int x; x = x, y > 0 | y < 2 \ No newline at end of file + - ==> \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 index 30f9b28acd0..66bebb3403e 100644 --- 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 @@ -3,4 +3,4 @@ key: | script: | instantiate var="x" with=3 hide:true; goals: - - ==> 3 > 0 + - ==> 3 > 0 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 index 0edb8b5ccf6..da74c45bd51 100644 --- 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 @@ -3,4 +3,4 @@ key: | script: | witness (\forall int x; (x>0 | x<2)) as="y"; goals: - - ==> \forall int x; x = x, y > 0 | y < 2 + - ==> \forall int x; x = x, y > 0 | y < 2 From 4f9ef7e0b20b95ac53c6fe3939db51ec0fad9d75 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 11 Oct 2025 00:15:54 +0200 Subject: [PATCH 67/77] accomodating "focus inside find" which is algorithmically not trivial! --- .../key/scripts/TermComparisonWithHoles.java | 139 +++++++++++------- .../key/scripts/TestProofScriptCommand.java | 6 +- .../de/uka/ilkd/key/scripts/cases/find2.yml | 3 +- .../ilkd/key/scripts/cases/findOfFocus.yml | 7 + .../java/org/key_project/logic/PosInTerm.java | 29 +++- 5 files changed, 117 insertions(+), 67 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/findOfFocus.yml 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 index a498b4cd685..6a7e9575f31 100644 --- 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 @@ -4,6 +4,7 @@ import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.logic.equality.RenamingTermProperty; import de.uka.ilkd.key.logic.op.*; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.key_project.logic.PosInTerm; import org.key_project.logic.Term; @@ -31,6 +32,7 @@ * * @author Mattias Ulbrich */ +@NullMarked public class TermComparisonWithHoles { private final JTerm referenceTerm; @@ -46,28 +48,53 @@ public final boolean matches(PosInOccurrence pio) { } PosInTerm focus = findFocus(referenceTerm); - if(focus != null) { - for(int i = focus.depth() -1; i >= 0; i--) { - if(pio.isTopLevel()) { - // focus is deeper than the current term - return false; - } - int focusIdx = focus.getIndexAt(i); - int termIdx = pio.posInTerm().getIndexAt(pio.depth() - 1); - if(focusIdx != termIdx) { - // the focus is not at the same position as the current term - return false; - } - pio = pio.up(); + 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; } - term = (JTerm) pio.subTerm(); } - return unifyHelp(referenceTerm, term, - ImmutableSLList.nil(), - ImmutableSLList.nil(), - null); + 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) { @@ -81,12 +108,20 @@ public final boolean matchesToplevel(SequentFormula sf) { 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 PosInTerm.of((char)i, subFocus); + return subFocus.prepend((char)i); } } return null; @@ -106,7 +141,7 @@ public final boolean matchesToplevel(SequentFormula sf) { private static boolean unifyHelp(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars, - NameAbstractionTable nat) { + @Nullable PosInTerm expectedFocus) { if (t0 == t1 && ownBoundVars.equals(cmpBoundVars)) { return true; @@ -120,15 +155,17 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, } else if(op.name().equals(HOLE_PREDICATE_NAME) || op.name().equals(HOLE_NAME)) { return true; } else if(op.name().equals(FOCUS_NAME)) { - // ignore the "focus" annotation - return unifyHelp(t0.sub(0), t1, ownBoundVars, cmpBoundVars, nat); + 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, deepAllSubs); + Set> deepAllSubs = new HashSet<>(); + computeSubterms(t1, expectedFocus, deepAllSubs); var lookfor = t0.sub(0); - var finalNat = nat; - return deepAllSubs.stream().anyMatch(t -> unifyHelp(lookfor, t, ownBoundVars, cmpBoundVars, finalNat)); + return deepAllSubs.stream().anyMatch(t -> unifyHelp(lookfor, t.first, ownBoundVars, cmpBoundVars, t.second)); } @@ -154,12 +191,14 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, // return false; // } - return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, nat); + return descendRecursively(t0, t1, ownBoundVars, cmpBoundVars, expectedFocus); } - private static void computeSubterms(JTerm t, Set deepAllSubs) { - deepAllSubs.add(t); - t.subs().stream().forEach(sub -> computeSubterms(sub, deepAllSubs)); + 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, @@ -173,31 +212,6 @@ private static boolean handleQuantifiableVariable(JTerm t0, JTerm t1, return true; } -// private static NameAbstractionTable handleJava(JTerm t0, JTerm t1, -// NameAbstractionTable nat) { -// -// if (!t0.javaBlock().isEmpty() || !t1.javaBlock().isEmpty()) { -// nat = checkNat(nat); -// if (!t0.javaBlock().equalsModRenaming(t1.javaBlock(), nat)) { -// return FAILED; -// } -// } -// -// if (!(t0.op() instanceof SchemaVariable) -// && t0.op() instanceof ProgramVariable) { -// if (!(t1.op() instanceof ProgramVariable)) { -// return FAILED; -// } -// nat = checkNat(nat); -// if (!((ProgramVariable) t0.op()).equalsModRenaming( -// (ProgramVariable) t1.op(), nat)) { -// return FAILED; -// } -// } -// -// return nat; -// } - /** * compare two quantifiable variables if they are equal modulo renaming * @@ -249,7 +263,7 @@ private static NameAbstractionTable checkNat(NameAbstractionTable nat) { private static boolean descendRecursively(JTerm t0, JTerm t1, ImmutableList ownBoundVars, ImmutableList cmpBoundVars, - NameAbstractionTable nat) { + PosInTerm expectedFocus) { for (int i = 0; i < t0.arity(); i++) { ImmutableList subOwnBoundVars = ownBoundVars; @@ -269,8 +283,10 @@ private static boolean descendRecursively(JTerm t0, JTerm t1, subCmpBoundVars = subCmpBoundVars.prepend(cmpVar); } + PosInTerm nextFocus = nextFocusPos(expectedFocus, i); + boolean newConstraint = unifyHelp(t0.sub(i), t1.sub(i), - subOwnBoundVars, subCmpBoundVars, nat); + subOwnBoundVars, subCmpBoundVars, nextFocus); if (!newConstraint) { return false; @@ -280,6 +296,15 @@ private static boolean descendRecursively(JTerm t0, JTerm t1, 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()) { 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 5413957143d..3ae06ad1e50 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 @@ -125,20 +125,20 @@ void testProofScript(TestInstance data, String name) throws Exception { Assertions.assertEquals(expected, goals.size()); for (String expectedGoal : data.goals()) { - assertThat(normaliseSpace(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+", " "); + return str.replaceAll("\\s+", " ").trim(); } } 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 index 3af65ac6ad8..498842ca2e8 100644 --- 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 @@ -1,7 +1,6 @@ key: | - \programVariables { int a; } \problem { ==> 1 + 0 = 1 & 2 + 0 = 2 & 3 + 0 = 3 } script: | rule add_zero_right on:(?find(2)); goals: - - ==> 1 + 0 = 1 & 0 + 2 + 5 = 7 \ No newline at end of file + - ==> 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.ncore/src/main/java/org/key_project/logic/PosInTerm.java b/key.ncore/src/main/java/org/key_project/logic/PosInTerm.java index b9b3c59ba1a..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,11 +82,11 @@ public static PosInTerm parseReverseString(String s) { : new PosInTerm(positions, (char) positions.length, true); } - public static PosInTerm of(char index, PosInTerm subPos) { - var newPositions = new char[subPos.size + 1]; - System.arraycopy(subPos.positions, 0, newPositions, 1, subPos.size); + 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) (subPos.size + 1), true); + return new PosInTerm(newPositions, (char) (size + 1), false); } /// returns the instance representing the top level position @@ -137,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 /// @@ -244,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()); From 900ec8975df8994ccd1d08c43b30e50ccf5b407f Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 19 Jul 2024 16:23:17 +0200 Subject: [PATCH 68/77] add "assumes" parameter to rule command not functional yet after cherry-picking --- .../de/uka/ilkd/key/scripts/EngineState.java | 5 + .../de/uka/ilkd/key/scripts/RuleCommand.java | 24 ++++- .../ilkd/key/scripts/SequentWithHoles.java | 94 +++++++++++++++++++ .../key/scripts/TermComparisonWithHoles.java | 1 + .../uka/ilkd/key/scripts/TermWithHoles.java | 23 ----- .../de/uka/ilkd/key/scripts/cases/assumes.yml | 7 ++ 6 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/assumes.yml 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 bf5556b4214..953393315da 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 @@ -94,6 +94,11 @@ private ValueInjector createDefaultValueInjector() { v.addConverter(TermWithHoles.class, String.class, (str) -> TermWithHoles.fromString(this, str)); + v.addConverter(SequentWithHoles.class, ProofScriptExpressionContext.class, + (ctx) -> SequentWithHoles.fromParserContext(this, ctx)); + v.addConverter(SequentWithHoles.class, String.class, + (str) -> SequentWithHoles.fromString(this, str)); + addContextTranslator(v, String.class); addContextTranslator(v, JTerm.class); addContextTranslator(v, Integer.class); 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 88b328815a0..ec7b247db41 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 @@ -25,6 +25,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; @@ -370,8 +371,9 @@ private List filterList(Services services, Parameters p, 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) { - boolean add = matcher == null || matcher.matches(pta.posInOccurrence()); + add = matcher == null || matcher.matches(pta.posInOccurrence()); for (var entry : pta.instantiations().getInstantiationMap()) { final SchemaVariable sv = entry.key(); @@ -383,6 +385,10 @@ private List filterList(Services services, Parameters p, || userInst.equalsModProperty(ptaInst, IRRELEVANT_TERM_LABELS_PROPERTY); } + if(tacletApp.assumesFormulaInstantiations() != null) { + add &= checkAssumes(p, tacletApp.assumesFormulaInstantiations(), services); + } + if (add) { matchingApps.add(pta); } @@ -391,6 +397,16 @@ private List filterList(Services services, Parameters p, 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. @@ -428,6 +444,12 @@ public static class Parameters { "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.") 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..18df53e38df --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java @@ -0,0 +1,94 @@ +/* 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.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.KeyAst; +import de.uka.ilkd.key.nparser.ParsingFacade; +import de.uka.ilkd.key.nparser.builder.ExpressionBuilder; +import de.uka.ilkd.key.util.parsing.BuildingIssue; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.ParserRuleContext; +import org.jspecify.annotations.NullMarked; +import org.key_project.logic.Name; +import org.key_project.logic.PosInTerm; +import org.key_project.logic.sort.AbstractSort; +import org.key_project.logic.sort.Sort; +import org.key_project.prover.rules.instantiation.AssumesFormulaInstantiation; +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.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +@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; + } +} \ No newline at end of file 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 index 6a7e9575f31..1b54e528ea0 100644 --- 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 @@ -10,6 +10,7 @@ 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.rules.instantiation.AssumesFormulaInstantiation; import org.key_project.prover.sequent.PosInOccurrence; import org.key_project.prover.sequent.Sequent; import org.key_project.prover.sequent.SequentFormula; 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 index 70bb08d5e47..d63358d6fb5 100644 --- 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 @@ -43,30 +43,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static de.uka.ilkd.key.strategy.StrategyProperties.*; - -/* - -y < (x+0) + y + (x+0), -a -> y < (x+0) + y + (x+0) - - -rule plus_zero on: ????? - ----> a -> y < (x+0) + y + FOCUS(x+0) - ----> _ -> (... _ + (_+0) ...) - ----> _ -> _find(_ + _focus(_ + 0)) - ----> ? -> ?find(? + ?focus(? + 0)) - ----> ?_ -> ?find(?_ + ?focus(_ + 0)) - */ @NullMarked public class TermWithHoles { 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 From aacfba1624d3b453dc3e4b91a8c2454b4a5f36cd Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 11 Oct 2025 01:26:12 +0200 Subject: [PATCH 69/77] bugfixing matching with program variables --- .../ilkd/key/scripts/TermComparisonWithHoles.java | 6 +----- .../uka/ilkd/key/scripts/cases/bugWithFunctions.yml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/bugWithFunctions.yml 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 index 1b54e528ea0..02b47be0e5c 100644 --- 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 @@ -179,11 +179,7 @@ private static boolean unifyHelp(JTerm t0, JTerm t1, final Operator op1 = t1.op(); - if (!(op0 instanceof ProgramVariable) && op0 != op1) { - return false; - } - - if (t0.sort() != t1.sort() || t0.arity() != t1.arity()) { + if (op0 != op1) { return false; } 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" + From 77e361f42fbd968647c88ca47d732792be301576 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 11 Oct 2025 01:42:13 +0200 Subject: [PATCH 70/77] updating the focus rule --- .../de/uka/ilkd/key/scripts/FocusCommand.java | 37 ++++--------------- .../ilkd/key/scripts/SequentWithHoles.java | 8 ++++ .../uka/ilkd/key/scripts/cases/focus_rule.yml | 6 +++ 3 files changed, 22 insertions(+), 29 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/focus_rule.yml 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 df279f4afea..0b12d0eaeda 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 @@ -67,15 +67,13 @@ public FocusCommand() { static class Parameters { @Argument @Documentation("The sequent containing the formulas to keep. It may contain placeholder symbols.") - public @MonotonicNonNull Sequent toKeep; + 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 @@ -89,38 +87,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/SequentWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/SequentWithHoles.java index 18df53e38df..6b67ab25ab2 100644 --- 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 @@ -91,4 +91,12 @@ public boolean matches(ImmutableList ifFormulaInsta 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)); + } } \ 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 From 25d2b798d0eb1976f671cd0034090fdcab22e0fa Mon Sep 17 00:00:00 2001 From: Wolfram Pfeifer Date: Tue, 13 Aug 2024 19:18:19 +0200 Subject: [PATCH 71/77] allow terms with holes in expand command adapted to new branch by MU --- .../de/uka/ilkd/key/scripts/ExpandDefCommand.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 index cebb06bb82f..8eca2a44b61 100644 --- 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 @@ -76,18 +76,15 @@ private TacletApp makeRuleApp(Parameters p, EngineState state) throws ScriptExce if (p.on != null) { apps = apps.filter( it -> it instanceof PosTacletApp && - ((JTerm) it.posInOccurrence().subTerm()).equalsModProperty(p.on, - TermLabelsProperty.TERM_LABELS_PROPERTY)); + p.on.matches(it.posInOccurrence())); } else if (p.formula != null) { apps = apps.filter( it -> it instanceof PosTacletApp && - ((JTerm) it.posInOccurrence().sequentFormula().formula()).equalsModProperty( - p.formula, TermLabelsProperty.TERM_LABELS_PROPERTY)); + 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) { @@ -107,11 +104,11 @@ private TacletApp makeRuleApp(Parameters p, EngineState state) throws ScriptExce public static class Parameters { @Option(value = "on") - public @Nullable JTerm on; + public @Nullable TermWithHoles on; @Option(value = "occ") public @Nullable Integer occ; @Option(value = "formula") - public @Nullable JTerm formula; + public @Nullable TermWithHoles formula; } From ae8b1417eaf274c7389affd89c50e46545f88394 Mon Sep 17 00:00:00 2001 From: Wolfram Pfeifer Date: Wed, 28 Aug 2024 17:05:28 +0200 Subject: [PATCH 72/77] added convenience macro to close all NPE and Out-of-bounds branches --- .../key/macros/TryCloseSideBranchesMacro.java | 77 +++++++++++++++++++ .../de.uka.ilkd.key.macros.ProofMacro | 1 + 2 files changed, 78 insertions(+) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/macros/TryCloseSideBranchesMacro.java 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..1b0dcd06674 --- /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/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 20f77f0e83f..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 From a09d6848cdde7ae5b2d5b7af5bad6d1c8606019e Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 11 Oct 2025 12:26:04 +0200 Subject: [PATCH 73/77] bug fixes in script engine --- key.core/build.gradle | 1 + .../uka/ilkd/key/nparser/ParsingFacade.java | 2 +- .../ilkd/key/proof/init/AbstractProfile.java | 2 + .../de/uka/ilkd/key/scripts/EngineState.java | 75 ++------- .../uka/ilkd/key/scripts/ExprEvaluator.java | 156 +++++++++++------- .../de/uka/ilkd/key/scripts/LetCommand.java | 51 +++--- .../key/scripts/TermComparisonWithHoles.java | 7 +- .../uka/ilkd/key/scripts/TermWithHoles.java | 28 +++- .../ilkd/key/scripts/meta/ValueInjector.java | 26 +-- .../key/scripts/TestProofScriptCommand.java | 18 +- .../key/scripts/meta/ValueInjectorTest.java | 37 ++++- .../de/uka/ilkd/key/scripts/cases/holes2.yml | 7 + .../de/uka/ilkd/key/scripts/cases/let.yml | 7 + .../ilkd/key/scripts/cases/termsAsStrings.yml | 8 + 14 files changed, 247 insertions(+), 178 deletions(-) create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/holes2.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/let.yml create mode 100644 key.core/src/test/resources/de/uka/ilkd/key/scripts/cases/termsAsStrings.yml 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/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/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/scripts/EngineState.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java index 953393315da..e20878e6d78 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 @@ -54,7 +54,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("."); @@ -81,73 +80,31 @@ public EngineState(Proof proof, ProofScriptEngine engine) { this.engine = engine; } - // Problem is: This is all KeY specific and there should be different converters - // for JML. But how to separate this? Probably do this externally after PSE generation. - // via some "enrichValueInjector" method in a different class ... + /// 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); - v.addConverter(TermWithHoles.class, ProofScriptExpressionContext.class, - (ctx) -> TermWithHoles.fromParserContext(this, ctx)); + + // to terms with holes v.addConverter(TermWithHoles.class, String.class, - (str) -> TermWithHoles.fromString(this, str)); + str -> TermWithHoles.fromString(this, str)); - v.addConverter(SequentWithHoles.class, ProofScriptExpressionContext.class, - (ctx) -> SequentWithHoles.fromParserContext(this, ctx)); + // to sequents with holes v.addConverter(SequentWithHoles.class, String.class, - (str) -> SequentWithHoles.fromString(this, str)); - - 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; - } + str -> SequentWithHoles.fromString(this, str)); - private void addContextTranslator(ValueInjector v, Class aClass) { - Converter converter = - (ProofScriptExpressionContext a) -> convertToString(v, aClass, a); - v.addConverter(aClass, ProofScriptExpressionContext.class, converter); + // 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) { @@ -374,10 +331,6 @@ public NamespaceSet getCurrentNamespaces() { } } - public ExprEvaluator getEvaluator() { - return exprEvaluator; - } - public void putUserData(String key, @Nullable Object val) { userData.put(key, val); } 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..9466c3edabb 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,15 +5,28 @@ import java.net.URI; +import de.uka.ilkd.key.logic.JTerm; import de.uka.ilkd.key.nparser.KeYParser; 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.Converter; +import de.uka.ilkd.key.scripts.meta.NoSpecifiedConverterException; +import de.uka.ilkd.key.scripts.meta.ValueInjector; +import de.uka.ilkd.key.util.ANTLRUtil; +import org.key_project.logic.sort.Sort; +import org.key_project.prover.sequent.Semisequent; import org.key_project.prover.sequent.Sequent; import org.antlr.v4.runtime.ParserRuleContext; +import org.key_project.prover.sequent.SequentFormula; +import org.key_project.prover.sequent.SequentKit; +import org.key_project.util.collection.ImmutableList; +import org.key_project.util.java.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +34,6 @@ /// Evaluates expression inside of proof script to their appropriate type. /// -/// - [JmlParser.ExpressionContext]: [Term] /// - [SeqContext]: [Sequent] /// - [Boolean_literalContext]: [Boolean] /// - [IntegerContext]: [Integer] @@ -31,7 +43,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 +51,102 @@ 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); + 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; } - @Override - public Object visitBoolean_literal(Boolean_literalContext ctx) { - return Boolean.parseBoolean(ctx.getText()); - } + 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 visitChar_literal(KeYParser.Char_literalContext ctx) { - return ctx.getText().charAt(1); // skip "'" } - @Override - public Object visitInteger(IntegerContext ctx) { - return Integer.parseInt(ctx.getText()); + 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 visitFloatLiteral(KeYParser.FloatLiteralContext ctx) { - return Float.parseFloat(ctx.getText()); + 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(0)); + } + } catch (Exception e) { + throw new ConversionException("Cannot convert expression to term: " + ANTLRUtil.reconstructOriginal(ctx)); + } } - @Override - public Object visitDoubleLiteral(DoubleLiteralContext ctx) { - return Double.parseDouble(ctx.getText()); + 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)); + } } - @Override - public String visitString_literal(String_literalContext ctx) { - return trim(ctx.getText(), '"'); + 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)); + } } - @Override - public Sequent visitSeq(SeqContext ctx) { - var expressionBuilder = - new ExpressionBuilder(state.getProof().getServices(), state.getCurrentNamespaces()); - expressionBuilder.setAbbrevMap(state.getAbbreviations()); - var t = (Sequent) ctx.accept(expressionBuilder); - var warnings = expressionBuilder.getBuildingIssues(); - warnings.forEach(it -> LOGGER.warn("{}", it)); - warnings.clear(); - return t; - - } - - @Override - public Object visitSimple_ident(Simple_identContext ctx) { - return evaluateExpression(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 - public Object visitTerm(KeYParser.TermContext ctx) { - return evaluateExpression(ctx); + private String convertToString(ProofScriptExpressionContext ctx) { + if (ctx.string_literal() != null) { + return StringUtil.trim(ctx.string_literal().getText(), '"'); + } else { + return ANTLRUtil.reconstructOriginal(ctx).trim(); + } } - 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; - } - - @Override - protected Object aggregateResult(Object aggregate, Object nextResult) { - return nextResult == null ? aggregate : nextResult; - } -} +} \ No newline at end of file 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 b061d0aba42..0faa7026522 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 @@ -11,9 +11,11 @@ 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; +import org.jspecify.annotations.Nullable; /// The *let* command lets you introduce entries to the abbreviation table. /// ``` @@ -33,34 +35,46 @@ /// * Apr,2025 (weigl): remove {@code force} in favor of {@code letf}. /// * Jan,2025 (weigl): add new parameter {@code force} to override bindings. @NullMarked -@Documentation(""" +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; + + let @abbrev1=term1 ... @abbrev2=term2; + or - letf @abbrev1=term1 ... @abbrev2=term2; + + 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 class LetCommand implements ProofScriptCommand { + + + 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(); } - @Override - public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args, - EngineState stateMap) throws ScriptException, InterruptedException { + public void execute(ScriptCommandAst ast) throws ScriptException, InterruptedException { + var args = state().getValueInjector().inject(new Parameters(), ast); - 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("@")) { // get rid of @ @@ -70,11 +84,9 @@ public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst arg 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); } @@ -87,11 +99,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/TermComparisonWithHoles.java b/key.core/src/main/java/de/uka/ilkd/key/scripts/TermComparisonWithHoles.java index 02b47be0e5c..984c44bf9f1 100644 --- 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 @@ -18,10 +18,7 @@ import org.key_project.util.collection.ImmutableSLList; import org.key_project.util.collection.Pair; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static de.uka.ilkd.key.scripts.TermWithHoles.*; @@ -39,7 +36,7 @@ public class TermComparisonWithHoles { private final JTerm referenceTerm; TermComparisonWithHoles(JTerm referenceTerm) { - this.referenceTerm = referenceTerm; + this.referenceTerm = Objects.requireNonNull(referenceTerm); } public final boolean matches(PosInOccurrence pio) { 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 index d63358d6fb5..c52e1115c6d 100644 --- 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 @@ -26,6 +26,7 @@ import de.uka.ilkd.key.util.parsing.BuildingIssue; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -40,20 +41,21 @@ import org.key_project.util.collection.ImmutableList; import org.key_project.util.collection.ImmutableSLList; import org.key_project.util.collection.ImmutableSet; +import org.key_project.util.java.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; @NullMarked public class TermWithHoles { private final JTerm term; public TermWithHoles(JTerm term) { - this.term = 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("?"); @@ -94,11 +96,27 @@ public boolean extendsTrans(Sort s) { } public static TermWithHoles fromString(EngineState engineState, String str) { - KeyAst.Term term = ParsingFacade.parseExpression(CharStreams.fromString(str)); - return fromParserContext(engineState, term.ctx); + 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, ParserRuleContext ctx) { + public static TermWithHoles fromParserContext(EngineState state, ParseTree ctx) { var expressionBuilder = new ExpressionBuilder(state.getProof().getServices(), enrichState(state)); expressionBuilder.setAbbrevMap(state.getAbbreviations()); 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 fbe40fbac3d..ea9088b1023 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 @@ -225,18 +225,19 @@ private List injectIntoField(ProofScriptArgument meta, ScriptCommandAst args, val = args.namedArgs().get(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())); + 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()); } - handled = List.of(meta.getName()); } try { @@ -293,7 +294,8 @@ public T convert(Class targetType, Object val) } catch (Exception e) { throw new ConversionException( String.format("Could not convert value '%s' from type '%s' to type '%s'", - val, val.getClass().getSimpleName(), targetType.getSimpleName()), + ScriptCommandAst.asReadableString(val), + val.getClass().getSimpleName(), targetType.getSimpleName()), e); } } 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 3ae06ad1e50..4eb88209a5c 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,6 +10,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; import de.uka.ilkd.key.control.DefaultUserInterfaceControl; import de.uka.ilkd.key.control.KeYEnvironment; @@ -21,6 +24,7 @@ import de.uka.ilkd.key.proof.Proof; import de.uka.ilkd.key.smt.newsmt2.MasterHandlerTest; +import org.jspecify.annotations.NonNull; import org.key_project.util.collection.ImmutableList; import com.fasterxml.jackson.databind.ObjectMapper; @@ -41,7 +45,7 @@ */ public class TestProofScriptCommand { - private static final String ONLY_CASE = null; + private static final String ONLY_CASES = System.getProperty("key.testProofScript.only"); private static final Logger LOGGER = LoggerFactory.getLogger(TestProofScriptCommand.class); public record TestInstance( @@ -57,8 +61,13 @@ public static List data() throws IOException, URISyntaxException { var folder = Paths.get("src/test/resources/de/uka/ilkd/key/scripts/cases") .toAbsolutePath(); - if(ONLY_CASE != null) { - folder = folder.resolve(ONLY_CASE); + 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)) { @@ -69,6 +78,9 @@ 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); 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 991dc9c3af4..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 @@ -67,6 +67,7 @@ public void testUnknownArguments() { 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)); @@ -79,21 +80,21 @@ public void testVarargsOld() throws Exception { Map args = new HashMap<>(); ScriptCommandAst ast = new ScriptCommandAst("pp", args, new LinkedList<>(), null); - args.put("#literal", "here goes the entire string..."); 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("21")); - assertEquals("otherString", pp.varargs.get("other")); + 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(); @@ -112,11 +113,29 @@ 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()); } } 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/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 From 3bb43320a10d6eb3c4c6058dd9c5435829290f00 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Sat, 11 Oct 2025 14:13:23 +0200 Subject: [PATCH 74/77] repairing JML script tests --- .../java/de/uka/ilkd/key/macros/ApplyScriptsMacro.java | 4 ++++ .../src/main/java/de/uka/ilkd/key/scripts/RuleCommand.java | 7 ++++++- .../test/java/de/uka/ilkd/key/scripts/JmlScriptTest.java | 2 +- .../resources/de/uka/ilkd/key/scripts/jml/AutoOnly2.java | 1 - 4 files changed, 11 insertions(+), 3 deletions(-) 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 index e9987ca40f1..d1ee6f6148c 100644 --- 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 @@ -27,6 +27,7 @@ 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; @@ -187,6 +188,9 @@ public ProofMacroFinishedInfo applyTo(UserInterfaceControl uic, Proof proof, 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"); 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 ec7b247db41..510584fefc7 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.*; @@ -255,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 { 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 index 0f67cc7ecc3..fa53950a474 100644 --- 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 @@ -37,7 +37,7 @@ public class JmlScriptTest { 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 = null; + 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; 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 index a3c11a11113..8be9e20db47 100644 --- 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 @@ -11,7 +11,6 @@ void test() { /*@ assert b && c ==> c || d \by { auto only:"beta"; - rule "close"; } */ } From 2bc3a36ce35158e5de8d92ea586a98b52f5b8ca5 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Wed, 15 Oct 2025 09:52:10 +0200 Subject: [PATCH 75/77] fixing a NPE bug in JavaProfile solution by Alexander Weigl --- .../strategy/SimplifyTermStrategy.java | 2 +- .../uka/ilkd/key/proof/init/JavaProfile.java | 21 +++++++++++++++++-- .../ilkd/key/gui/StrategySelectionView.java | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) 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/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.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 From 46567da57e2063be79ce60ed21c941b3ee433a9f Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 24 Oct 2025 15:18:07 +0200 Subject: [PATCH 76/77] improving symbex only macro --- .../ilkd/key/macros/SymbolicExecutionOnlyMacro.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 index 1b876922f45..61496fa5cfa 100644 --- 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 @@ -4,6 +4,7 @@ package de.uka.ilkd.key.macros; import de.uka.ilkd.key.control.TermLabelVisibilityManager; +import de.uka.ilkd.key.logic.op.Junctor; import de.uka.ilkd.key.logic.op.ObserverFunction; import de.uka.ilkd.key.logic.op.UpdateApplication; import de.uka.ilkd.key.proof.Goal; @@ -119,11 +120,20 @@ public static boolean isAdmittedRule(RuleApp ruleApp) { 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); } - return term.op() instanceof Modality; + 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 { From ec5929d9d07ad15825a3df906130ec25331835c0 Mon Sep 17 00:00:00 2001 From: Mattias Ulbrich Date: Fri, 24 Oct 2025 16:29:24 +0200 Subject: [PATCH 77/77] adding an infinity catch on "throw null" in symb ex. --- .../macros/SymbolicExecutionOnlyMacro.java | 8 ++++- .../de/uka/ilkd/key/proof/rules/javaRules.key | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) 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 index 61496fa5cfa..52d33cf2ab2 100644 --- 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 @@ -152,12 +152,18 @@ public Name name() { @Override public boolean isApprovedApp(RuleApp app, PosInOccurrence pio, Goal goal) { - if(!hasModality(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); } 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..13f4bc3fb39 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,26 @@ 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)) + + // 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" };