Skip to content

Commit 5aa36a1

Browse files
authored
Higher Order Proof Scripts (#3654)
2 parents 3f35bf9 + ff35a38 commit 5aa36a1

File tree

10 files changed

+177
-81
lines changed

10 files changed

+177
-81
lines changed

key.core/src/main/antlr4/KeYParser.g4

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -859,13 +859,12 @@ proofScriptEntry
859859
| LBRACE proofScript RBRACE
860860
)
861861
;
862+
862863
proofScriptEOF: proofScript EOF;
863-
proofScript: proofScriptCommand+;
864-
proofScriptCommand: cmd=IDENT proofScriptParameters?
865-
( LBRACE sub=proofScript RBRACE SEMI?
866-
| SEMI);
864+
proofScript: proofScriptCommand*;
865+
proofScriptCommand: cmd=IDENT proofScriptParameters SEMI;
867866

868-
proofScriptParameters: proofScriptParameter+;
867+
proofScriptParameters: proofScriptParameter*;
869868
proofScriptParameter : ((pname=proofScriptParameterName (COLON|EQUALS))? expr=proofScriptExpression);
870869
proofScriptParameterName: AT? IDENT; // someone thought, that the let-command parameters should have a leading "@"
871870
proofScriptExpression:
@@ -878,8 +877,10 @@ proofScriptExpression:
878877
| simple_ident
879878
| abbreviation
880879
| literals
880+
| proofScriptCodeBlock
881881
;
882882

883+
proofScriptCodeBlock: LBRACE proofScript RBRACE;
883884

884885
// PROOF
885886
proof: PROOF EOF;

key.core/src/main/java/de/uka/ilkd/key/nparser/KeyAst.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import de.uka.ilkd.key.nparser.builder.IncludeFinder;
1818
import de.uka.ilkd.key.parser.Location;
1919
import de.uka.ilkd.key.proof.init.Includes;
20+
import de.uka.ilkd.key.scripts.ScriptBlock;
2021
import de.uka.ilkd.key.scripts.ScriptCommandAst;
2122
import de.uka.ilkd.key.settings.Configuration;
2223
import de.uka.ilkd.key.settings.ProofSettings;
@@ -253,7 +254,11 @@ public static class ProofScript extends KeyAst<KeYParser.ProofScriptContext> {
253254
}
254255

255256
public URI getUri() {
256-
final var sourceName = ctx.start.getTokenSource().getSourceName();
257+
return getUri(ctx.start);
258+
}
259+
260+
public static URI getUri(Token token) {
261+
final var sourceName = token.getTokenSource().getSourceName();
257262
try {
258263
if (sourceName.startsWith("file:") || sourceName.startsWith("http:")
259264
|| sourceName.startsWith("jar:"))
@@ -273,31 +278,45 @@ public List<ScriptCommandAst> asAst() {
273278
return asAst(fileUri, ctx.proofScriptCommand());
274279
}
275280

276-
private List<ScriptCommandAst> asAst(URI file,
281+
private static List<ScriptCommandAst> asAst(URI file,
277282
List<KeYParser.ProofScriptCommandContext> cmds) {
278283
return cmds.stream().map(it -> asAst(file, it)).toList();
279284
}
280285

281-
private @NonNull ScriptCommandAst asAst(URI file, KeYParser.ProofScriptCommandContext it) {
282-
var loc = new Location(file, Position.fromToken(it.start));
283-
var sub = it.sub != null
284-
? asAst(file, it.sub.proofScriptCommand())
285-
: null;
286+
public static @NonNull ScriptBlock asAst(URI file,
287+
KeYParser.ProofScriptCodeBlockContext ctx) {
288+
var loc = new Location(file, Position.fromToken(ctx.start));
289+
final var proofScriptCommandContexts = ctx.proofScript().proofScriptCommand();
290+
final List<ScriptCommandAst> list =
291+
proofScriptCommandContexts.stream()
292+
.map(it -> asAst(file, it))
293+
.toList();
294+
return new ScriptBlock(list, loc);
295+
}
286296

297+
private static @NonNull ScriptCommandAst asAst(URI file,
298+
KeYParser.ProofScriptCommandContext it) {
299+
var loc = new Location(file, Position.fromToken(it.start));
287300
var nargs = new HashMap<String, Object>();
288301
var pargs = new ArrayList<>();
289302

290303
if (it.proofScriptParameters() != null) {
291304
for (var param : it.proofScriptParameters().proofScriptParameter()) {
305+
var expr = param.expr;
306+
Object value = expr;
307+
if (expr.proofScriptCodeBlock() != null) {
308+
value = asAst(file, expr.proofScriptCodeBlock());
309+
}
310+
292311
if (param.pname != null) {
293-
nargs.put(param.pname.getText(), param.expr);
312+
nargs.put(param.pname.getText(), value);
294313
} else {
295-
pargs.add(param.expr);
314+
pargs.add(value);
296315
}
297316
}
298317
}
299318

300-
return new ScriptCommandAst(it.cmd.getText(), nargs, pargs, sub, loc);
319+
return new ScriptCommandAst(it.cmd.getText(), nargs, pargs, loc);
301320
}
302321
}
303322
}

key.core/src/main/java/de/uka/ilkd/key/scripts/AllCommand.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import de.uka.ilkd.key.scripts.meta.ProofScriptArgument;
1111

1212
public class AllCommand implements ProofScriptCommand {
13-
private String documentation;
14-
1513
@Override
1614
public List<ProofScriptArgument> getArguments() {
1715
return List.of();
@@ -20,21 +18,21 @@ public List<ProofScriptArgument> getArguments() {
2018
@Override
2119
public void execute(AbstractUserInterfaceControl uiControl, ScriptCommandAst args,
2220
EngineState stateMap) throws ScriptException, InterruptedException {
23-
var block = args.commands();
24-
25-
if (block == null) {
26-
throw new ScriptException("Missing command to apply onAll to");
21+
if (args.positionalArgs().size() != 1) {
22+
throw new ScriptException(
23+
"Invalid number of positional arguments to 'onAll'. Pos. arguments: "
24+
+ args.positionalArgs().size());
2725
}
2826

27+
var block = stateMap.getValueInjector().convert(
28+
args.positionalArgs().getFirst(),
29+
ScriptBlock.class);
30+
2931
var proof = stateMap.getProof();
30-
// Node selectedNode = state.getSelectedNode();
3132
for (Goal g : proof.openGoals()) {
32-
// if (isBelow(g, selectedNode)) {
3333
stateMap.setGoal(g);
3434
stateMap.getEngine().execute(uiControl, block);
35-
// }
3635
}
37-
// state.setGoal(selectedNode);
3836
}
3937

4038

key.core/src/main/java/de/uka/ilkd/key/scripts/EngineState.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ private ValueInjector createDefaultValueInjector() {
100100
addContextTranslator(v, JTerm.class);
101101
addContextTranslator(v, Sequent.class);
102102
addContextTranslator(v, Semisequent.class);
103+
addContextTranslator(v, ScriptBlock.class);
103104
return v;
104105
}
105106

key.core/src/main/java/de/uka/ilkd/key/scripts/ExprEvaluator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
* SPDX-License-Identifier: GPL-2.0-only */
44
package de.uka.ilkd.key.scripts;
55

6+
import java.net.URI;
7+
68
import de.uka.ilkd.key.nparser.KeYParser;
79
import de.uka.ilkd.key.nparser.KeYParser.*;
810
import de.uka.ilkd.key.nparser.KeYParserBaseVisitor;
11+
import de.uka.ilkd.key.nparser.KeyAst;
912
import de.uka.ilkd.key.nparser.builder.ExpressionBuilder;
1013

1114
import org.key_project.prover.sequent.Sequent;
@@ -36,6 +39,12 @@ class ExprEvaluator extends KeYParserBaseVisitor<Object> {
3639
this.state = engineState;
3740
}
3841

42+
@Override
43+
public Object visitProofScriptCodeBlock(ProofScriptCodeBlockContext ctx) {
44+
URI uri = KeyAst.ProofScript.getUri(ctx.start);
45+
return KeyAst.ProofScript.asAst(uri, ctx);
46+
}
47+
3948
@Override
4049
public Object visitBoolean_literal(Boolean_literalContext ctx) {
4150
return Boolean.parseBoolean(ctx.getText());

key.core/src/main/java/de/uka/ilkd/key/scripts/ProofScriptEngine.java

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import de.uka.ilkd.key.proof.Proof;
2323

2424
import org.antlr.v4.runtime.RuleContext;
25-
import org.antlr.v4.runtime.misc.Interval;
2625
import org.jspecify.annotations.Nullable;
2726
import org.slf4j.Logger;
2827
import org.slf4j.LoggerFactory;
@@ -32,9 +31,6 @@
3231
* @author Alexander Weigl
3332
*/
3433
public class ProofScriptEngine {
35-
public static final int KEY_START_INDEX_PARAMETER = 2;
36-
public static final String KEY_SUB_SCRIPT = "#block";
37-
private static final int MAX_CHARS_PER_COMMAND = 80;
3834
private static final Map<String, ProofScriptCommand> COMMANDS = loadCommands();
3935
private static final Logger LOGGER = LoggerFactory.getLogger(ProofScriptEngine.class);
4036

@@ -117,6 +113,11 @@ public void execute(AbstractUserInterfaceControl uiControl, Proof proof)
117113
execute(uiControl, script);
118114
}
119115

116+
public void execute(AbstractUserInterfaceControl uiControl, ScriptBlock block)
117+
throws ScriptException, InterruptedException {
118+
execute(uiControl, block.commands());
119+
}
120+
120121
public void execute(AbstractUserInterfaceControl uiControl, List<ScriptCommandAst> commands)
121122
throws InterruptedException, ScriptException {
122123
if (script.isEmpty()) { // no commands given, no work to do
@@ -199,29 +200,6 @@ public static String prettyPrintCommand(KeYParser.ProofScriptCommandContext ctx)
199200
}
200201

201202

202-
private Map<String, Object> getArguments(KeYParser.ProofScriptCommandContext commandContext) {
203-
var map = new TreeMap<String, Object>();
204-
int i = KEY_START_INDEX_PARAMETER;
205-
206-
if (commandContext.proofScriptParameters() != null) {
207-
for (var pc : commandContext.proofScriptParameters().proofScriptParameter()) {
208-
String key = pc.pname != null ? pc.pname.getText() : "#" + (i++);
209-
map.put(key, pc.expr);
210-
}
211-
}
212-
213-
if (commandContext.sub != null) {
214-
map.put(KEY_SUB_SCRIPT, commandContext.sub);
215-
}
216-
217-
var in = commandContext.start.getTokenSource().getInputStream();
218-
var txt = in.getText(
219-
Interval.of(commandContext.start.getStartIndex(), commandContext.stop.getStopIndex()));
220-
map.put(ScriptLineParser.LITERAL_KEY, txt);
221-
return map;
222-
}
223-
224-
225203
public EngineState getStateMap() {
226204
return stateMap;
227205
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* This file is part of KeY - https://key-project.org
2+
* KeY is licensed under the GNU General Public License Version 2
3+
* SPDX-License-Identifier: GPL-2.0-only */
4+
package de.uka.ilkd.key.scripts;
5+
6+
import java.util.List;
7+
8+
import de.uka.ilkd.key.parser.Location;
9+
10+
import org.jspecify.annotations.NullMarked;
11+
import org.jspecify.annotations.Nullable;
12+
13+
import static java.util.stream.Collectors.joining;
14+
15+
/// This class represents a block `{ c1; c2; ... ck; }` of proof scripts.
16+
@NullMarked
17+
public record ScriptBlock(
18+
List<ScriptCommandAst> commands,
19+
@Nullable Location location) {
20+
21+
/// Renders this command a parsable string representation. The order of the arguments is as
22+
/// follows:
23+
/// key-value arguments, positional arguments and the additional script block.
24+
///
25+
/// @see de.uka.ilkd.key.nparser.ParsingFacade#parseScript(org.antlr.v4.runtime.CharStream)
26+
public String asCommandLine() {
27+
if (commands.isEmpty()) {
28+
return " {}";
29+
}
30+
if (commands.size() == 1) {
31+
return " {" + commands.getFirst().asCommandLine() + "}";
32+
}
33+
34+
return " {\n"
35+
+ commands.stream().map(ScriptCommandAst::asCommandLine).collect(joining("\n"))
36+
+ "\n}";
37+
}
38+
}

key.core/src/main/java/de/uka/ilkd/key/scripts/ScriptCommandAst.java

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
* SPDX-License-Identifier: GPL-2.0-only */
44
package de.uka.ilkd.key.scripts;
55

6-
import java.util.Collections;
76
import java.util.List;
87
import java.util.Map;
98
import java.util.Objects;
109

10+
import de.uka.ilkd.key.nparser.KeYParser;
11+
import de.uka.ilkd.key.nparser.KeyAst;
1112
import de.uka.ilkd.key.parser.Location;
1213

1314
import org.antlr.v4.runtime.ParserRuleContext;
@@ -20,33 +21,36 @@
2021
///
2122
/// It is an abstraction to the commands of following structure:
2223
/// ```
23-
/// <commandName> key_1:value_1 ... key_m:value_m positionalArgs_1 ... positionalArgs_n {
24-
/// commands_0; ...; commands_k;
25-
/// }
24+
/// integer-split
25+
/// "<0" : { auto; }
26+
/// "=0" : { instantiate; }
27+
/// ">0" : { tryclose; }
28+
/// ;
2629
/// ```
30+
/// or
31+
/// ```
32+
/// <commandName> key_1:value_1 ... key_m:value_m positionalArgs_1 ... positionalArgs_n;
33+
/// ```
34+
/// where `value_X` and `positionalArgs_X` can also be scripts.
2735
///
2836
/// @param commandName the name of the command, e.g., "macro" for `macro auto;`
2937
/// @param namedArgs a map of the given named arguments and values.
3038
/// If a named argument is not given, the entry should be missing in the map. Null-values are not
3139
/// allowed.
3240
/// @param positionalArgs the list of given positional arguments
33-
/// @param commands a nullable block of proof script arguments (represents "higher-order proof
34-
/// scripts").
35-
/// If null, the block was omitted syntactically in contrast to an empty list.
36-
/// @param location the location of this command for error reporting.
41+
/// @param location the location of this command for error reporting. **excluded from equality**
3742
/// @author Alexander Weigl
3843
/// @version 1 (14.03.25)
3944
@NullMarked
4045
public record ScriptCommandAst(
4146
String commandName,
4247
Map<String, Object> namedArgs,
4348
List<Object> positionalArgs,
44-
@Nullable List<ScriptCommandAst> commands,
4549
@Nullable Location location) {
4650

4751
public ScriptCommandAst(String commandName, Map<String, Object> namedArgs,
4852
List<Object> positionalArgs) {
49-
this(commandName, namedArgs, positionalArgs, Collections.emptyList(), null);
53+
this(commandName, namedArgs, positionalArgs, null);
5054
}
5155

5256
/// Renders this command a parsable string representation. The order of the arguments is as
@@ -56,24 +60,45 @@ public ScriptCommandAst(String commandName, Map<String, Object> namedArgs,
5660
/// @see de.uka.ilkd.key.nparser.ParsingFacade#parseScript(CharStream)
5761
public String asCommandLine() {
5862
return commandName + ' ' +
59-
namedArgs.entrySet().stream()
60-
.map(it -> it.getKey() + ": " + asReadableString(it.getValue()))
61-
.collect(joining(" "))
62-
+ ' '
63-
+ positionalArgs.stream().map(ScriptCommandAst::asReadableString)
64-
.collect(joining(" "))
65-
+ (commands != null
66-
? " {"
67-
+ commands.stream().map(ScriptCommandAst::asCommandLine)
68-
.collect(joining("\n"))
69-
+ "\n}"
70-
: ";");
63+
namedArgs.entrySet().stream()
64+
.map(it -> it.getKey() + ": " + asReadableString(it.getValue()))
65+
.collect(joining(" "))
66+
+ ' '
67+
+ positionalArgs.stream().map(ScriptCommandAst::asReadableString)
68+
.collect(joining(" "))
69+
+ ";";
7170
}
7271

7372
public static String asReadableString(Object value) {
73+
if (value instanceof ScriptBlock b) {
74+
return b.asCommandLine();
75+
}
76+
77+
if (value instanceof KeYParser.ProofScriptCodeBlockContext ctx) {
78+
asReadableString(KeyAst.ProofScript.asAst(null, ctx));
79+
}
7480
if (value instanceof ParserRuleContext ctx) {
7581
return ctx.getText();
7682
}
7783
return Objects.toString(value);
7884
}
85+
86+
@Override
87+
public int hashCode() {
88+
return Objects.hash(commandName, namedArgs, positionalArgs);
89+
}
90+
91+
@Override
92+
public boolean equals(Object obj) {
93+
if (this == obj) {
94+
return true;
95+
}
96+
if (obj == null || getClass() != obj.getClass()) {
97+
return false;
98+
}
99+
ScriptCommandAst other = (ScriptCommandAst) obj;
100+
return Objects.equals(commandName, other.commandName)
101+
&& Objects.equals(positionalArgs, other.positionalArgs)
102+
&& Objects.equals(namedArgs, other.namedArgs);
103+
}
79104
}

0 commit comments

Comments
 (0)