How to add a proof macro¶
2026 — the code below was compiled against the current KeY sources and executed: a problem file whose proof script invokes the macro closes with "Proved".
A proof macro bundles strategy invocations and rule applications into one
high-level proof step. Macros appear in the GUI (context menu Strategy
macros) and are callable from proof scripts
via macro "<name>"; — you get both for free from one implementation.
(See the user documentation of the shipped macros.)
1. Where to put the code¶
Macros live in any module on the classpath. Either add the class to
key.core (package de.uka.ilkd.key.macros) if it is of general use, or —
for a self-contained feature — put it into your own keyext.<name> module
(see How to add an extension for the module skeleton
and the build-file wiring in settings.gradle and key.ui/build.gradle;
a macro-only module just needs implementation project(":key.core")).
2. Pick a base class¶
Base class (package de.uka.ilkd.key.macros) |
Use when |
|---|---|
SequentialProofMacro |
your macro is a sequence of existing macros |
StrategyProofMacro |
your macro is "run the auto strategy, but modified" (filtered rules, different costs) |
AbstractProofMacro |
full control over canApplyTo/applyTo |
3. Example¶
This macro tries to close all open goals with a step limit, by composing
the existing TryCloseMacro:
/* 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 org.key_project.demo;
import de.uka.ilkd.key.macros.ProofMacro;
import de.uka.ilkd.key.macros.SequentialProofMacro;
import de.uka.ilkd.key.macros.TryCloseMacro;
public class DemoMacro extends SequentialProofMacro {
@Override
protected ProofMacro[] createProofMacroArray() {
return new ProofMacro[] { new TryCloseMacro(5000) };
}
@Override
public String getName() {
return "Demo: try to close all goals";
}
@Override
public String getCategory() {
return "Demo"; // submenu in the GUI's "Strategy macros" menu
}
@Override
public String getScriptCommandName() {
return "demo-close"; // name for `macro "demo-close";` in scripts
}
@Override
public String getDescription() {
return "Tries to close all open goals using at most 5000 rule applications per goal.";
}
}
For a StrategyProofMacro example look at FinishSymbolicExecutionMacro
(runs the strategy until no modality is left) or
PropositionalExpansionMacro (restricts the strategy to an explicit list
of admitted rules); both are in key.core.
4. Register the macro¶
Macros are discovered via ServiceLoader. Add the fully qualified class
name to the file
src/main/resources/META-INF/services/de.uka.ilkd.key.macros.ProofMacro
of your module (one class per line). Without this file the macro neither shows up in the GUI nor resolves in scripts.
5. Verify¶
Compile (./gradlew :keyext.demo:compileJava), then check both fronts:
- GUI:
./gradlew :key.ui:run, load any problem, right-click a goal — the macro appears under Strategy macros → Demo. - Script (headless): create a problem file with an embedded script and run it in batch mode:
\predicates { p; q; }
\problem { (p & q) -> (q & p) }
\proofScript {
macro "demo-close";
}
./gradlew :key.ui:run --args='--auto demoScript.key'
# ... ConsoleUserInterfaceControl Proved
Quote hyphenated names in scripts
In the script language, a macro name containing - (like
demo-close) must be written as a string: macro "demo-close";.
A bare identifier works only for names without special characters.
Notes¶
getName()is the human-readable label,getScriptCommandName()the script identifier (returnnullif the macro should not be scriptable),getCategory()groups macros in the GUI menu.- Macros can accept parameters from scripts
(
macro "name" key:"value";) by overridinghasParameter(String)andsetParameter(String, String)fromProofMacro— seeAutoMacrofor a real example. - Long-running macros run on a worker thread; they must react to interruption so users can cancel them.