diff --git a/.github/workflows/nightlydeploy.yml b/.github/workflows/nightlydeploy.yml
index 953ee83037a..e6b76fbbb25 100644
--- a/.github/workflows/nightlydeploy.yml
+++ b/.github/workflows/nightlydeploy.yml
@@ -9,8 +9,11 @@ permissions:
contents: write
id-token: write
+# The documentation generation for varconds and proof script commands
+# extracts `///` markdown javadocs via therapi, which requires JDK 23+.
+# `--release=21` is still set!
env:
- JAVA_VERSION: 21
+ JAVA_VERSION: 25
jobs:
@@ -29,7 +32,7 @@ jobs:
uses: gradle/actions/setup-gradle@v6
- name: Build with Gradle
- run: ./gradlew --parallel assemble javadoc alldoc
+ run: ./gradlew --parallel assemble javadoc alldoc :key.core:generateVarcondDoc :key.core:generateScriptDoc
- name: Upload ShadowJars (fat and slim)
uses: actions/upload-artifact@v7
@@ -40,6 +43,14 @@ jobs:
path: "*/build/libs/*-exe.jar"
retention-days: 1
+ - name: Upload varcond and proof script documentation
+ uses: actions/upload-artifact@v7
+ with:
+ name: varcond-script-docs
+ path: "key.core/build/docs/keydocs/*"
+ retention-days: 30
+ compression-level: '9'
+
- name: Package
run: tar cvf key-javadoc.tar.xz build/docs/javadoc
diff --git a/build.gradle b/build.gradle
index 93b792ef77c..c89c5127e6a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -83,7 +83,6 @@ subprojects {
implementation(libs.slf4j.api)
testImplementation(libs.logback.classic)
-
compileOnly(libs.jspecify)
testCompileOnly(libs.jspecify)
compileOnly(libs.checkerframework.qual)
@@ -316,7 +315,6 @@ subprojects {
afterEvaluate {
description = project.description
assert (project.description != null && project.description != "") : "Description of modules are required at maven-central"
- print(project.description)
}
url = 'https://key-project.org/'
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7ddff7e2730..8038c479dfe 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -25,6 +25,7 @@ stringtemplate = "4.3.4"
javapoet = "1.13.0"
javaparser = "3.28.0-K13.6"
truth = "1.4.5"
+therapi = "0.13.0"
# UI and CLI
flatlaf = "3.7.2"
@@ -35,6 +36,7 @@ docking-frames = "1.1.3p1"
# External tools
scala-isabelle = "0.4.5"
+
[libraries]
# SLF4J and logging
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
@@ -43,6 +45,10 @@ logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "lo
# JSpecify (nullability annotations)
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
+#Extracting Javadoc
+therapi-processor = { module = "com.github.therapi:therapi-runtime-javadoc-scribe", version.ref = "therapi" }
+therapi-runtime = { module = "com.github.therapi:therapi-runtime-javadoc", version.ref = "therapi" }
+
# EISOP Checker Framework
checkerframework-qual = { module = "io.github.eisop:checker-qual", version.ref = "eisop" }
checkerframework-util = { module = "io.github.eisop:checker-util", version.ref = "eisop" }
diff --git a/key.core/build.gradle b/key.core/build.gradle
index c6584ca35ab..f12b7330bac 100644
--- a/key.core/build.gradle
+++ b/key.core/build.gradle
@@ -24,6 +24,9 @@ dependencies {
testImplementation(libs.truth)
testImplementation(project(":key.core"))
+ annotationProcessor(libs.therapi.processor)
+ api(libs.therapi.runtime)
+
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
testImplementation(libs.jackson.dataformat.yaml)
@@ -32,7 +35,7 @@ dependencies {
}
tasks.withType(JavaCompile).configureEach {
- options.compilerArgs << "-Ajavadoc.packages=de.uka.ilkd.key.scripts"
+ options.compilerArgs << "-Ajavadoc.packages=de.uka.ilkd.key.scripts,de.uka.ilkd.key.rule.conditions"
}
// The target directory for JavaCC (parser generation)
@@ -67,6 +70,38 @@ tasks.register('generateSMTListings') {
}
}
+// Generates the markdown documentation of all variable conditions (varconds),
+// see de.uka.ilkd.key.doc.VarcondDoc.
+tasks.register('generateVarcondDoc', JavaExec) {
+ group = 'documentation'
+ description = 'Generates the markdown documentation of all variable conditions (varconds).'
+ classpath = sourceSets.main.runtimeClasspath
+ mainClass = 'de.uka.ilkd.key.doc.VarcondDoc'
+ def mdFile = layout.buildDirectory.file("docs/keydocs/varconds.md")
+ def jsonFile = layout.buildDirectory.file("docs/keydocs/varconds.json")
+ outputs.file(mdFile)
+ outputs.file(jsonFile)
+ doFirst {
+ args = [mdFile.get().asFile.absolutePath, jsonFile.get().asFile.absolutePath ]
+ }
+}
+
+// Generates the markdown documentation of all proof script commands,
+// see de.uka.ilkd.key.doc.ScriptDoc.
+tasks.register('generateScriptDoc', JavaExec) {
+ group = 'documentation'
+ description = 'Generates the markdown documentation of all proof script commands.'
+ classpath = sourceSets.main.runtimeClasspath
+ mainClass = 'de.uka.ilkd.key.doc.ScriptDoc'
+ def mdFile = layout.buildDirectory.file("docs/keydocs/script-commands.md")
+ def jsonFile = layout.buildDirectory.file("docs/keydocs/script-commands.json")
+ outputs.file(mdFile)
+ outputs.file(jsonFile)
+ doFirst {
+ args = [mdFile.get().asFile.absolutePath, jsonFile.get().asFile.absolutePath ]
+ }
+}
+
tasks.register('generateSolverPropsList') {
def pack = "de/uka/ilkd/key/smt/solvertypes"
def resourcesDir = "${project.projectDir}/src/main/resources"
diff --git a/key.core/src/main/java/de/uka/ilkd/key/doc/AbstractDocGenerator.java b/key.core/src/main/java/de/uka/ilkd/key/doc/AbstractDocGenerator.java
new file mode 100644
index 00000000000..f73fbbbcb1b
--- /dev/null
+++ b/key.core/src/main/java/de/uka/ilkd/key/doc/AbstractDocGenerator.java
@@ -0,0 +1,202 @@
+/* This file is part of KeY - https://key-project.org
+ * KeY is licensed under the GNU General Public License Version 2,
+ * or (at your option) any later version.
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+package de.uka.ilkd.key.doc;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Base class for the documentation generators of KeY's taclet language constructs, e.g., for the
+ * variable conditions ({@link VarcondDoc}) and the proof script commands ({@link ScriptDoc}). It
+ * provides the shared handling of the output target and the post-processing of javadoc text.
+ *
+ * @author Alexander Weigl
+ * @version 1 (23.08.26)
+ */
+public abstract class AbstractDocGenerator {
+
+ /// Runs this generator. If a command line argument is given, the markdown output is written
+ /// to that file together with a JSON dump ([#generateJsonData()]) next to it; otherwise the
+ /// markdown is printed to stdout.
+ ///
+ /// @param mdFile an optional single argument with the path of the output file
+ /// @param jsonFile
+ /// @throws IOException if the output file cannot be written
+ protected void run(String mdFile, String jsonFile) throws IOException {
+ Path mdPath = Path.of(mdFile);
+ if (mdPath.getParent() != null) {
+ Files.createDirectories(mdPath.getParent());
+ }
+
+ try (var out = new PrintStream(Files.newOutputStream(mdPath), false)) {
+ generateDocumentation(out);
+ }
+
+
+ Object data = generateJsonData();
+
+ if (data != null) {
+ Path jsonPath = Paths.get(jsonFile);
+ if (jsonPath.getParent() != null) {
+ Files.createDirectories(jsonPath.getParent());
+ }
+ try (var out = new PrintWriter(Files.newOutputStream(jsonPath), false)) {
+ JsonWriter.toJson(data, out);
+ }
+ }
+ }
+
+ /// Writes the documentation of this generator in markdown format.
+ protected abstract void generateDocumentation(PrintStream out);
+
+ /// Returns the documentation data of this generator in a structured form, which is serialized
+ /// into a JSON dump next to the markdown file. Returns `null` if this generator does not
+ /// support a JSON dump.
+ ///
+ /// @return the structured documentation data, or `null`
+ protected @Nullable Object generateJsonData() {
+ return null;
+ }
+
+ /// Converts a javadoc comment into markdown by replacing common HTML tags and inline tags
+ /// with their markdown equivalents.
+ ///
+ /// @param it the javadoc comment, may be null
+ /// @return the comment as markdown
+ protected static String cleanJavadoc(@Nullable String it) {
+ if (it == null) {
+ it = "";
+ }
+ return it.replace("", "`")
+ .replace("", "\n")
+ .replace("", "\n")
+ .replace("- ", "* ")
+ .replace("
", "")
+ .replace("{@link", "`")
+ .replace("}", "`")
+ .replace("", "`")
+ .replace("
", "`")
+ .replace("", "`")
+ .replace("", "**")
+ .replace("", "**");
+ }
+
+ /// Indents each line of the given text by the given prefix.
+ ///
+ /// @param spaces the indentation prefix
+ /// @param it the text to indent
+ /// @return the indented text
+ protected static String indent(String spaces, String it) {
+ return spaces + it.replace("\n", "\n" + spaces);
+ }
+}
+
+
+/**
+ * A minimal, dependency-free JSON serializer supporting the data structures used by the
+ * documentation generators: {@link Map}s, {@link Iterable}s, strings, numbers, booleans, and
+ * {@code null}. Other objects are serialized via their {@link Object#toString()}.
+ *
+ * @author Alexander Weigl
+ * @version 1 (10.10.26)
+ */
+final class JsonWriter {
+
+ private JsonWriter() {
+ }
+
+ /**
+ * Serializes the given value into a pretty-printed JSON string.
+ *
+ * @param value the value to serialize
+ * @param out target to write into
+ */
+ public static void toJson(@Nullable Object value, PrintWriter out) {
+ write(out, value, 0);
+ }
+
+ private static void write(PrintWriter sb, @Nullable Object value, int depth) {
+ switch (value) {
+ case null -> sb.append("null");
+ case String s -> writeString(sb, s);
+ case Boolean b -> sb.print(b);
+ case Number n -> sb.print(n);
+ case Map, ?> map -> writeObject(sb, map, depth);
+ case Iterable> iterable -> writeArray(sb, iterable, depth);
+ default -> writeString(sb, value.toString());
+ }
+ }
+
+ private static void writeObject(PrintWriter sb, Map, ?> map, int depth) {
+ sb.append("{\n");
+ var it = map.entrySet().iterator();
+ while (it.hasNext()) {
+ var entry = it.next();
+ indent(sb, depth + 1);
+ writeString(sb, String.valueOf(entry.getKey()));
+ sb.append(": ");
+ write(sb, entry.getValue(), depth + 1);
+ if (it.hasNext()) {
+ sb.append(',');
+ }
+ sb.append('\n');
+ }
+ indent(sb, depth);
+ sb.append('}');
+ }
+
+ private static void writeArray(PrintWriter sb, Iterable> iterable, int depth) {
+ sb.append("[\n");
+ var it = iterable.iterator();
+ while (it.hasNext()) {
+ indent(sb, depth + 1);
+ write(sb, it.next(), depth + 1);
+ if (it.hasNext()) {
+ sb.append(',');
+ }
+ sb.append('\n');
+ }
+ indent(sb, depth);
+ sb.append(']');
+ }
+
+ private static void writeString(PrintWriter sb, String s) {
+ sb.append('"');
+ for (var i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"' -> sb.append("\\\"");
+ case '\\' -> sb.append("\\\\");
+ case '\n' -> sb.append("\\n");
+ case '\r' -> sb.append("\\r");
+ case '\t' -> sb.append("\\t");
+ case '\b' -> sb.append("\\b");
+ case '\f' -> sb.append("\\f");
+ default -> {
+ if (c < 0x20) {
+ sb.append("\\u%04x".formatted((int) c));
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+ }
+ sb.append('"');
+ }
+
+ private static void indent(PrintWriter sb, int depth) {
+ sb.append(" ".repeat(depth));
+ }
+}
+\newLocalVars(varDeclsSV: VARIABLE, updateBeforeSV: VARIABLE, updateFrameSV: VARIABLE, bodySV: VARIABLE)
+
diff --git a/key.core/src/main/java/de/uka/ilkd/key/doc/ScriptDoc.java b/key.core/src/main/java/de/uka/ilkd/key/doc/ScriptDoc.java
new file mode 100644
index 00000000000..69b610f9c25
--- /dev/null
+++ b/key.core/src/main/java/de/uka/ilkd/key/doc/ScriptDoc.java
@@ -0,0 +1,80 @@
+/* This file is part of KeY - https://key-project.org
+ * KeY is licensed under the GNU General Public License Version 2,
+ * or (at your option) any later version.
+ * SPDX-License-Identifier: GPL-2.0-or-later */
+package de.uka.ilkd.key.doc;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.uka.ilkd.key.scripts.ProofScriptCommand;
+import de.uka.ilkd.key.scripts.ProofScriptEngine;
+import de.uka.ilkd.key.scripts.meta.ProofScriptArgument;
+
+/**
+ * Generates the markdown documentation of all proof script commands (loaded via
+ * {@link ProofScriptEngine#loadCommands()}), based on their self-documentation.
+ *
+ * @author Alexander Weigl
+ * @version 1 (23.08.26)
+ */
+public class ScriptDoc extends AbstractDocGenerator {
+
+ public static void main(String[] args) throws Exception {
+ new ScriptDoc().run(args[0], args[1]);
+ }
+
+ @Override
+ protected void generateDocumentation(PrintStream out) {
+ for (var command : sortedCommands()) {
+ out.println();
+ out.println();
+ out.format("### `%s`\n\n", command.getName());
+ out.println(cleanJavadoc(command.getDocumentation()));
+ }
+ }
+
+ @Override
+ protected Object generateJsonData() {
+ var commands = new ArrayList