diff --git a/.gitignore b/.gitignore
index 26b5b9756..b9a075332 100644
--- a/.gitignore
+++ b/.gitignore
@@ -98,20 +98,27 @@ bin-test
processing-examples
# Maven ignores
+.kotlin
.gradle
-core/build/
-build/publish/
-app/build
-java/build/
+.build/
+/core/build/
+/build/publish/
+/app/build
+/java/build/
/build/reports
/java/bin
/java/libraries/svg/bin
/java/preprocessor/build
/java/lsp/build
-/.kotlin/sessions
-/core/examples/build
-
-.build/
-/app/windows/obj
/java/gradle/build
+/core/examples/build
/java/gradle/example/.processing
+/app/windows/obj
+/java/android/example/build
+/java/android/example/.processing
+/java/gradle/example/build
+/java/gradle/example/gradle/wrapper/gradle-wrapper.jar
+/java/gradle/example/gradle/wrapper/gradle-wrapper.properties
+/java/gradle/example/gradlew
+/java/gradle/example/gradlew.bat
+/java/gradle/example/.kotlin/errors
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index e9be69039..2db2e88c8 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -26,5 +26,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/ant/processing/app/gradle/GradleService.java b/app/ant/processing/app/gradle/GradleService.java
new file mode 100644
index 000000000..0beca36ab
--- /dev/null
+++ b/app/ant/processing/app/gradle/GradleService.java
@@ -0,0 +1,15 @@
+package processing.app.gradle;
+
+import processing.app.ui.Editor;
+
+public class GradleService {
+ public GradleService(Editor editor) { }
+
+ public void setEnabled(boolean enabled) {}
+ public boolean getEnabled() { return false; }
+ public void prepare(){}
+ public void run() {}
+ public void export(){}
+ public void stop() {}
+ public void startService() {}
+}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5323a1a82..6df826fdb 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,8 @@
import org.gradle.internal.jvm.Jvm
+import org.gradle.kotlin.dsl.support.zipTo
import org.gradle.internal.os.OperatingSystem
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask
import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download
@@ -49,14 +51,18 @@ compose.desktop {
application {
mainClass = "processing.app.ProcessingKt"
- jvmArgs(*listOf(
- Pair("processing.version", rootProject.version),
- Pair("processing.revision", findProperty("revision") ?: Int.MAX_VALUE),
- Pair("processing.contributions.source", "https://contributions.processing.org/contribs"),
- Pair("processing.download.page", "https://processing.org/download/"),
- Pair("processing.download.latest", "https://processing.org/download/latest.txt"),
- Pair("processing.tutorials", "https://processing.org/tutorials/"),
- ).map { "-D${it.first}=${it.second}" }.toTypedArray())
+
+ val variables = mapOf(
+ "processing.group" to (rootProject.group.takeIf { it != "" } ?: "processing"),
+ "processing.version" to rootProject.version,
+ "processing.revision" to (findProperty("revision") ?: Int.MAX_VALUE),
+ "processing.contributions.source" to "https://contributions.processing.org/contribs",
+ "processing.download.page" to "https://processing.org/download/",
+ "processing.download.latest" to "https://processing.org/download/latest.txt",
+ "processing.tutorials" to "https://processing.org/tutorials/"
+ )
+
+ jvmArgs(*variables.entries.map { "-D${it.key}=${it.value}" }.toTypedArray())
nativeDistributions{
modules("jdk.jdi", "java.compiler", "jdk.accessibility", "java.management.rmi", "java.scripting")
@@ -110,6 +116,7 @@ dependencies {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
+ implementation(compose.materialIconsExtended)
implementation(compose.desktop.currentOs)
@@ -117,13 +124,13 @@ dependencies {
implementation(libs.kaml)
implementation(libs.markdown)
implementation(libs.markdownJVM)
+ implementation(gradleApi())
+ implementation(libs.clikt)
testImplementation(kotlin("test"))
testImplementation(libs.mockitoKotlin)
testImplementation(libs.junitJupiter)
testImplementation(libs.junitJupiterParams)
-
- implementation(libs.clikt)
}
tasks.test {
@@ -405,23 +412,6 @@ tasks.register("includeJavaModeResources") {
from(java.layout.buildDirectory.dir("resources-bundled"))
into(composeResources("../"))
}
-// TODO: Move to java mode
-tasks.register("renameWindres") {
- dependsOn("includeSharedAssets","includeJavaModeResources")
- val dir = composeResources("modes/java/application/launch4j/bin/")
- val os = DefaultNativePlatform.getCurrentOperatingSystem()
- val platform = when {
- os.isWindows -> "windows"
- os.isMacOsX -> "macos"
- else -> "linux"
- }
- from(dir) {
- include("*-$platform*")
- rename("(.*)-$platform(.*)", "$1$2")
- }
- duplicatesStrategy = DuplicatesStrategy.INCLUDE
- into(dir)
-}
tasks.register("includeProcessingResources"){
dependsOn(
"includeJdk",
@@ -430,8 +420,7 @@ tasks.register("includeProcessingResources"){
"includeSharedAssets",
"includeProcessingExamples",
"includeProcessingWebsiteExamples",
- "includeJavaModeResources",
- "renameWindres"
+ "includeJavaModeResources"
)
finalizedBy("signResources")
}
@@ -510,9 +499,9 @@ tasks.register("signResources"){
}
file(composeResources("Info.plist")).delete()
}
-
-
}
+
+
tasks.register("setExecutablePermissions") {
description = "Sets executable permissions on binaries in Processing.app resources"
group = "compose desktop"
@@ -537,6 +526,8 @@ tasks.register("setExecutablePermissions") {
afterEvaluate {
tasks.named("prepareAppResources").configure {
dependsOn("includeProcessingResources")
+ // Make sure all libraries are bundled in the maven repository distributed with the app
+ dependsOn(listOf("core","java:preprocessor", "java:gradle").map { project(":$it").tasks.named("publishAllPublicationsToAppRepository") })
}
tasks.named("createDistributable").configure {
finalizedBy("setExecutablePermissions")
diff --git a/app/src/main/resources/defaults.txt b/app/src/main/resources/defaults.txt
index 6e3e00f0d..431988bb7 100644
--- a/app/src/main/resources/defaults.txt
+++ b/app/src/main/resources/defaults.txt
@@ -186,6 +186,9 @@ console.temp.days = 7
console.scrollback.lines = 500
console.scrollback.chars = 40000
+# run java sketches with Gradle aka the Modern Build System
+run.use_gradle = false
+
# Any additional Java options when running.
# If you change this and can't run things, it's your own durn fault.
run.options =
diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java
index d55c8b710..ad67ffe8f 100644
--- a/app/src/processing/app/Language.java
+++ b/app/src/processing/app/Language.java
@@ -183,7 +183,6 @@ public class Language {
return instance;
}
-
static private String get(String key) {
LanguageBundle bundle = init().bundle;
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index 640c77ead..076506296 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -393,6 +393,8 @@ public class Preferences {
static protected void setSketchbookPath(String path) {
+ // Unify path seperator for all platforms
+ path = path.replace(File.separatorChar, '/');
set("sketchbook.path.four", path); //$NON-NLS-1$
}
}
diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index 02465c8b5..e503db465 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -10,6 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.option
+import processing.app.gradle.api.Sketch
import processing.app.ui.Start
class Processing: SuspendingCliktCommand("processing"){
@@ -40,7 +41,8 @@ suspend fun main(args: Array){
Processing()
.subcommands(
LSP(),
- LegacyCLI(args)
+ LegacyCLI(args),
+ Sketch()
)
.main(args)
}
diff --git a/app/src/processing/app/gradle/Debugger.kt b/app/src/processing/app/gradle/Debugger.kt
new file mode 100644
index 000000000..aea18ec13
--- /dev/null
+++ b/app/src/processing/app/gradle/Debugger.kt
@@ -0,0 +1,43 @@
+package processing.app.gradle
+
+import com.sun.jdi.Bootstrap
+import com.sun.jdi.VirtualMachine
+import com.sun.jdi.connect.AttachingConnector
+import kotlinx.coroutines.delay
+import processing.app.Messages
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.TimeSource
+
+class Debugger {
+ companion object {
+ suspend fun connect(port: Int?): VirtualMachine? {
+ try {
+ Messages.log("Attaching to VM $port")
+ val connector = Bootstrap.virtualMachineManager().allConnectors()
+ .firstOrNull { it.name() == "com.sun.jdi.SocketAttach" }
+ as AttachingConnector?
+ ?: throw IllegalStateException("No socket attach connector found")
+ val args = connector.defaultArguments()
+ args["port"]?.setValue(port?.toString() ?: "5005")
+
+ // Try to attach the debugger, retrying if it fails
+ val start = TimeSource.Monotonic.markNow()
+ while (start.elapsedNow() < 10.seconds) {
+ try {
+ val sketch = connector.attach(args)
+ sketch.resume()
+ Messages.log("Attached to VM: ${sketch.name()}")
+ return sketch
+ } catch (e: Exception) {
+ Messages.log("Error while attaching to VM: ${e.message}... Retrying")
+ }
+ delay(250)
+ }
+ } catch (e: Exception) {
+ Messages.log("Error while attaching to VM: ${e.message}")
+ return null
+ }
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/Exceptions.kt b/app/src/processing/app/gradle/Exceptions.kt
new file mode 100644
index 000000000..947d720a2
--- /dev/null
+++ b/app/src/processing/app/gradle/Exceptions.kt
@@ -0,0 +1,103 @@
+package processing.app.gradle
+
+import com.sun.jdi.ObjectReference
+import com.sun.jdi.StackFrame
+import com.sun.jdi.StringReference
+import com.sun.jdi.VirtualMachine
+import com.sun.jdi.event.ExceptionEvent
+import com.sun.jdi.request.EventRequest
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import processing.app.Messages
+
+// TODO: Consider adding a panel to the footer
+class Exceptions {
+ companion object {
+ suspend fun listen(vm: VirtualMachine) {
+ try {
+ val manager = vm.eventRequestManager()
+
+ val request = manager.createExceptionRequest(null, false, true)
+ request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD)
+ request.enable()
+
+ val queue = vm.eventQueue()
+ while (true) {
+ val eventSet = queue.remove()
+ for (event in eventSet) {
+ if (event is ExceptionEvent) {
+ printExceptionDetails(event)
+ event.thread().resume()
+ }
+ }
+ eventSet.resume()
+ delay(10)
+ }
+ } catch (e: Exception) {
+ Messages.log("Error while listening for exceptions: ${e.message}")
+ }
+ }
+
+ fun printExceptionDetails(event: ExceptionEvent) {
+ val exception = event.exception()
+ val thread = event.thread()
+ val location = event.location()
+ val stackFrames = thread.frames()
+
+ println("\nπ¨ Exception Caught π¨")
+ println("Type : ${exception.referenceType().name()}")
+ // TODO: Fix exception message retrieval
+// println("Message : ${getExceptionMessage(exception)}")
+ println("Thread : ${thread.name()}")
+ println("Location : ${location.sourcePath()}:${location.lineNumber()}\n")
+
+ // TODO: Map to .pde file again
+ // TODO: Communicate back to Editor
+
+ // Separate stack frames
+ val userFrames = mutableListOf()
+ val processingFrames = mutableListOf()
+
+ stackFrames.forEach { frame ->
+ val className = frame.location().declaringType().name()
+ if (className.startsWith("processing.")) {
+ processingFrames.add(frame)
+ } else {
+ userFrames.add(frame)
+ }
+ }
+
+ // Print user frames first
+ println("π Stacktrace (Your Code First):")
+ userFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
+
+ // Print Processing frames second
+ if (processingFrames.isNotEmpty()) {
+ println("\nπ§ Processing Stacktrace (Hidden Initially):")
+ processingFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
+ }
+
+ println("ββββββββββββββββββββββββββββββββββ\n")
+ }
+
+ fun printStackFrame(index: Int, frame: StackFrame) {
+ val location = frame.location()
+ val method = location.method()
+ println(
+ " #$index ${location.sourcePath()}:${location.lineNumber()} -> ${
+ method.declaringType().name()
+ }.${method.name()}()"
+ )
+ }
+
+ // Extracts the exception's message
+ fun getExceptionMessage(exception: ObjectReference): String {
+ val messageMethod = exception.referenceType().methodsByName("getMessage").firstOrNull() ?: return "Unknown"
+ val messageValue =
+ exception.invokeMethod(null, messageMethod, emptyList(), ObjectReference.INVOKE_SINGLE_THREADED)
+ return (messageValue as? StringReference)?.value() ?: "Unknown"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradleJob.kt b/app/src/processing/app/gradle/GradleJob.kt
new file mode 100644
index 000000000..d67055a25
--- /dev/null
+++ b/app/src/processing/app/gradle/GradleJob.kt
@@ -0,0 +1,177 @@
+package processing.app.gradle
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import com.sun.jdi.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.problems.ProblemEvent
+import org.gradle.tooling.events.problems.Severity
+import org.gradle.tooling.events.problems.internal.DefaultFileLocation
+import org.gradle.tooling.events.problems.internal.DefaultSingleProblemEvent
+import org.gradle.tooling.events.task.TaskFinishEvent
+import org.gradle.tooling.events.task.TaskStartEvent
+import processing.app.Base
+import processing.app.Messages
+import java.io.InputStreamReader
+import java.io.PipedInputStream
+import java.io.PipedOutputStream
+import java.lang.IllegalStateException
+
+// TODO: Move the error reporting to its own file
+// TODO: Move the output filtering to its own file
+class GradleJob{
+ enum class State{
+ NONE,
+ BUILDING,
+ RUNNING,
+ DONE
+ }
+
+ var service: GradleService? = null
+ var configure: BuildLauncher.() -> Unit = {}
+
+ val state = mutableStateOf(State.NONE)
+ val vm = mutableStateOf(null)
+ val problems = mutableStateListOf()
+
+ private val scope = CoroutineScope(Dispatchers.IO)
+ private val cancel = GradleConnector.newCancellationTokenSource()
+
+ private val outputStream = PipedOutputStream()
+ private val errorStream = PipedOutputStream()
+
+ fun start() {
+ val folder = service?.sketch?.folder ?: throw IllegalStateException("Sketch folder is not set")
+ scope.launch {
+ try {
+ state.value = State.BUILDING
+
+ GradleConnector.newConnector()
+ .forProjectDirectory(folder)
+ .connect()
+ .newBuild()
+ .apply {
+ configure()
+ withCancellationToken(cancel.token())
+ addStateListener()
+ addDebugging()
+ setStandardOutput(outputStream)
+ setStandardError(errorStream)
+ run()
+ }
+ }catch (e: Exception){
+ Messages.log("Error while running: ${e.message} ${e.cause?.message}")
+ }finally {
+ state.value = State.DONE
+ vm.value = null
+ }
+ }
+ // TODO: I'm sure this can be done better
+ scope.launch {
+ try {
+ InputStreamReader(PipedInputStream(outputStream)).buffered().use { reader ->
+ reader.lineSequence()
+ .forEach { line ->
+ if (cancel.token().isCancellationRequested) {
+ return@launch
+ }
+ if (state.value != State.RUNNING) {
+ return@forEach
+ }
+ service?.out?.println(line)
+ }
+ }
+ }catch (e: Exception){
+ Messages.log("Error while reading output: ${e.message}")
+ }
+ }
+ scope.launch {
+ try {
+ InputStreamReader(PipedInputStream(errorStream)).buffered().use { reader ->
+ reader.lineSequence()
+ .forEach { line ->
+ if (cancel.token().isCancellationRequested) {
+ return@launch
+ }
+ if (state.value != State.RUNNING) {
+ return@forEach
+ }
+ when{
+ line.contains("+[IMKClient subclass]: chose IMKClient_Modern") -> return@forEach
+ line.contains("+[IMKInputSession subclass]: chose IMKInputSession_Modern") -> return@forEach
+ line.startsWith("__MOVE__") -> return@forEach
+ else -> service?.err?.println(line)
+ }
+ }
+ }
+ }catch (e: Exception){
+ Messages.log("Error while reading error: ${e.message}")
+ }
+ }
+
+ }
+
+ fun cancel(){
+ cancel.cancel()
+ }
+ private fun BuildLauncher.addStateListener(){
+ addProgressListener(ProgressListener { event ->
+ if(event is TaskStartEvent) {
+ when(event.descriptor.name) {
+ ":run" -> {
+ state.value = State.RUNNING
+ Messages.log("Start run")
+ }
+ }
+
+ }
+ if(event is TaskFinishEvent) {
+ when(event.descriptor.name){
+ ":jar"->{
+ state.value = State.NONE
+ Messages.log("Jar finished")
+ }
+ ":run"->{
+ state.value = State.NONE
+ }
+ }
+ }
+ if(event is DefaultSingleProblemEvent) {
+ // TODO: Move to UI instead of printing
+ if(event.definition.severity == Severity.ADVICE) return@ProgressListener
+ problems.add(event)
+
+ val path = (event.locations.firstOrNull() as DefaultFileLocation?)?.path
+
+ val header = """
+ ${event.definition.id.displayName}:
+ ${event.contextualLabel.contextualLabel}
+ """.trimIndent()
+
+ val details = event.details.details?.replace(path ?: "", "")
+ val solutions = event.solutions.joinToString("\n") { it.solution }
+ val content = "$header\n$details\n$solutions"
+ service?.err?.println(content)
+ }
+ })
+ }
+
+ fun BuildLauncher.addDebugging() {
+ addProgressListener(ProgressListener { event ->
+ if (event !is TaskStartEvent) return@ProgressListener
+ if (event.descriptor.name != ":run") return@ProgressListener
+
+ scope.launch {
+ val debugger = Debugger.connect(service?.debugPort) ?: return@launch
+ vm.value = debugger
+ Exceptions.listen(debugger)
+ }
+
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradleService.kt b/app/src/processing/app/gradle/GradleService.kt
new file mode 100644
index 000000000..af04f884b
--- /dev/null
+++ b/app/src/processing/app/gradle/GradleService.kt
@@ -0,0 +1,214 @@
+package processing.app.gradle
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import org.gradle.tooling.BuildLauncher
+import processing.app.Base
+import processing.app.Language
+import processing.app.Messages
+import processing.app.Mode
+import processing.app.Platform
+import processing.app.Preferences
+import processing.app.Sketch
+import processing.app.ui.Editor
+import java.io.*
+import kotlin.io.path.createTempDirectory
+import kotlin.io.path.deleteIfExists
+import kotlin.io.path.writeText
+
+// TODO: Test offline mode, gradle seems to be included as not needed to be downloaded.
+// TODO: Test running examples
+// TODO: Report failures to the console
+// TODO: Highlight errors in the editor
+
+// TODO: ---- FUTURE ----
+// TODO: Improve progress tracking
+// TODO: PoC new debugger/tweak mode
+// TODO: Allow for plugins to skip gradle entirely / new modes
+// TODO: Add background building
+// TODO: Track build speed (for analytics?)
+
+// The gradle service runs the gradle tasks and manages the gradle connection
+// It will create the necessary build files for gradle to run
+// Then it will kick off a new GradleJob to run the tasks
+// GradleJob manages the gradle build and connects the debugger
+class GradleService(
+ val mode: Mode,
+ val editor: Editor?,
+) {
+ val active = mutableStateOf(Preferences.getBoolean("run.use_gradle"))
+
+ var sketch: Sketch? = null
+
+ var out: PrintStream = System.out
+ var err: PrintStream = System.err
+
+ val jobs = mutableStateListOf()
+ val workingDir = createTempDirectory()
+ val debugPort = (30_000..60_000).random()
+
+ // TODO: Add support for present
+ fun run(){
+ stopActions()
+
+ val job = GradleJob()
+ job.service = this
+ job.configure = {
+ setup()
+ forTasks("run")
+ }
+ jobs.add(job)
+ job.start()
+ }
+
+ fun export(){
+ stopActions()
+
+ val job = GradleJob()
+ job.service = this
+ job.configure = {
+ setup()
+ forTasks("runDistributable")
+ }
+ jobs.add(job)
+ job.start()
+ }
+
+ fun stop(){
+ stopActions()
+ }
+
+ fun stopActions(){
+ jobs
+ .forEach(GradleJob::cancel)
+ }
+
+ private fun setupGradle(): MutableList {
+ val sketch = sketch ?: throw IllegalStateException("Sketch is not set")
+
+ val unsaved = sketch.code
+ .map { code ->
+ val file = workingDir.resolve("unsaved/${code.fileName}")
+ file.parent.toFile().mkdirs()
+ // If tab is marked modified save it to the working directory
+ // Otherwise delete the file
+ if(code.isModified){
+ file.writeText(code.documentText)
+ }else{
+ file.deleteIfExists()
+ }
+ return@map code.fileName
+ }
+
+ val group = System.getProperty("processing.group", "org.processing")
+
+ val variables = mapOf(
+ "group" to group,
+ "version" to Base.getVersionName(),
+ "sketchFolder" to sketch.folder.absolutePath,
+ "sketchbook" to Base.getSketchbookFolder(),
+ "workingDir" to workingDir.toAbsolutePath().toString(),
+ "settings" to Platform.getSettingsFolder().absolutePath.toString(),
+ "unsaved" to unsaved.joinToString(","),
+ "debugPort" to debugPort.toString(),
+ "fullscreen" to false, // TODO: Implement
+ "display" to 1, // TODO: Implement
+ "external" to true,
+ "editor.location" to editor?.location?.let { "${it.x},${it.y}" },
+ //"awt.disable" to false,
+ //"window.color" to "0xFF000000", // TODO: Implement
+ //"stop.color" to "0xFF000000", // TODO: Implement
+ "stop.hide" to false, // TODO: Implement
+ "sketch.folder" to sketch.folder.absolutePath,
+ )
+ val repository = Platform.getContentFile("repository").absolutePath.replace("""\""", """\\""")
+
+ val initGradle = workingDir.resolve("init.gradle.kts").apply {
+ val content = """
+ beforeSettings{
+ pluginManagement {
+ repositories {
+ maven { url = uri("$repository") }
+ gradlePluginPortal()
+ }
+ }
+ }
+ allprojects{
+ repositories {
+ maven { url = uri("$repository") }
+ mavenCentral()
+ }
+ }
+ """.trimIndent()
+
+ writeText(content)
+ }
+
+
+ val buildGradle = sketch.folder.resolve("build.gradle.kts")
+ val generate = buildGradle.let {
+ if(!it.exists()) return@let true
+
+ val contents = it.readText()
+ if(!contents.contains("@processing-auto-generated")) return@let false
+
+ val version = contents.substringAfter("version=").substringBefore("\n")
+ if(version != Base.getVersionName()) return@let true
+
+ val modeTitle = contents.substringAfter("mode=").substringBefore(" ")
+ if(this.mode.title != modeTitle) return@let true
+
+ return@let Base.DEBUG
+ }
+ if (generate) {
+ Messages.log("build.gradle.kts outdated or not found in ${sketch.folder}, creating one")
+ val header = """
+ // @processing-auto-generated mode=${mode.title} version=${Base.getVersionName()}
+ //
+ """.trimIndent()
+
+ // TODO: add instructions keys
+ val instructions = Language.text("gradle.instructions")
+ .split("\n")
+ .joinToString("\n") { "// $it" }
+
+ val configuration = """
+ plugins{
+ id("org.processing.gradle") version "${Base.getVersionName()}"
+ }
+ """.trimIndent()
+ val content = "${header}\n${instructions}\n${configuration}"
+ buildGradle.writeText(content)
+ }
+ val settingsGradle = sketch.folder.resolve("settings.gradle.kts")
+ if (!settingsGradle.exists()) {
+ settingsGradle.createNewFile()
+ }
+
+ val arguments = mutableListOf("--init-script", initGradle.toAbsolutePath().toString())
+ if (!Base.DEBUG) arguments.add("--quiet")
+ arguments.addAll(variables.entries
+ .filter { it.value != null }
+ .map { "-Pprocessing.${it.key}=${it.value}" }
+ )
+
+ return arguments
+ }
+
+
+ private fun BuildLauncher.setup(extraArguments: List = listOf()) {
+ setJavaHome(Platform.getJavaHome())
+
+ val arguments = setupGradle()
+ arguments.addAll(extraArguments)
+ withArguments(*arguments.toTypedArray())
+ }
+
+ // Hooks for java to check if the Gradle service is running since mutableStateOf is not accessible in java
+ fun getEnabled(): Boolean {
+ return active.value
+ }
+ fun setEnabled(active: Boolean) {
+ this.active.value = active
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/api/Sketch.kt b/app/src/processing/app/gradle/api/Sketch.kt
new file mode 100644
index 000000000..04cccbb27
--- /dev/null
+++ b/app/src/processing/app/gradle/api/Sketch.kt
@@ -0,0 +1,63 @@
+package processing.app.gradle.api
+
+import com.github.ajalt.clikt.command.SuspendingCliktCommand
+import com.github.ajalt.clikt.core.Context
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import processing.app.Base
+import processing.app.Platform
+import processing.app.Preferences
+import processing.app.contrib.ModeContribution
+import processing.app.gradle.GradleJob
+import processing.app.gradle.GradleService
+
+class Sketch : SuspendingCliktCommand("sketch") {
+ init {
+ subcommands(
+ Run()
+ )
+ }
+
+ override fun help(context: Context): String {
+ return """Manage sketches in the Processing environment."""
+ }
+
+ override suspend fun run() {
+ System.setProperty("java.awt.headless", "true")
+ }
+
+ class Run : SuspendingCliktCommand(name = "run") {
+ val sketch by option("--sketch", help = "The sketch to run")
+ .required()
+
+ override fun help(context: Context): String {
+ return "Run the Processing sketch."
+ }
+
+ override suspend fun run() {
+ Base.setCommandLine()
+ Platform.init()
+ Preferences.init()
+ Base.locateSketchbookFolder()
+
+ // TODO: Support modes other than Java
+ val mode = ModeContribution.load(
+ null, Platform.getContentFile("modes/java"),
+ "processing.mode.java.JavaMode"
+ ).mode ?: throw IllegalStateException("Java mode not found")
+
+ System.setProperty("java.awt.headless", "false")
+
+ val service = GradleService(mode,null)
+ service.sketch = processing.app.Sketch(sketch, mode)
+ service.run()
+
+ // TODO: Use an async way to wait for the job to finish
+ //Wait for the service to finish
+ while (service.jobs.any { it.state.value != GradleJob.State.DONE }) {
+ Thread.sleep(100)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java
index a06cbe238..7201642ce 100644
--- a/app/src/processing/app/ui/Editor.java
+++ b/app/src/processing/app/ui/Editor.java
@@ -48,7 +48,6 @@ import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.undo.*;
-import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.SystemInfo;
import processing.app.Base;
import processing.app.Formatter;
@@ -63,6 +62,7 @@ import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.SketchException;
import processing.app.contrib.ContributionManager;
+import processing.app.gradle.GradleService;
import processing.app.laf.PdeMenuItemUI;
import processing.app.syntax.*;
import processing.core.*;
@@ -75,6 +75,7 @@ public abstract class Editor extends JFrame implements RunnerListener {
protected Base base;
protected EditorState state;
protected Mode mode;
+ protected GradleService service;
// There may be certain gutter sizes that cause text bounds
// inside the console to be calculated incorrectly.
@@ -157,6 +158,7 @@ public abstract class Editor extends JFrame implements RunnerListener {
this.base = base;
this.state = state;
this.mode = mode;
+ this.service = new GradleService(this.mode,this);
// Make sure Base.getActiveEditor() never returns null
base.checkFirstEditor(this);
@@ -389,6 +391,9 @@ public abstract class Editor extends JFrame implements RunnerListener {
return ef;
}
+ public EditorFooter getFooter() {
+ return footer;
+ }
public void addErrorTable(EditorFooter ef) {
JScrollPane scrollPane = new JScrollPane();
@@ -478,6 +483,9 @@ public abstract class Editor extends JFrame implements RunnerListener {
return mode;
}
+ public GradleService getService() {
+ return service;
+ }
public void repaintHeader() {
header.repaint();
@@ -588,6 +596,7 @@ public abstract class Editor extends JFrame implements RunnerListener {
* with things in the Preferences window.
*/
public void applyPreferences() {
+ service.setEnabled(Preferences.getBoolean("run.use_gradle"));
// Even though this is only updating the theme (colors, icons),
// subclasses use this to apply other preferences.
// For instance, Java Mode applies changes to error checking.
@@ -2265,6 +2274,7 @@ public abstract class Editor extends JFrame implements RunnerListener {
} catch (IOException e) {
throw new EditorException("Could not create the sketch.", e);
}
+ service.setSketch(sketch);
header.rebuild();
updateTitle();
diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java
index c8c40ee48..f2d9de97f 100644
--- a/app/src/processing/app/ui/EditorConsole.java
+++ b/app/src/processing/app/ui/EditorConsole.java
@@ -82,6 +82,11 @@ public class EditorConsole extends JScrollPane {
sketchOut = new PrintStream(new EditorConsoleStream(false));
sketchErr = new PrintStream(new EditorConsoleStream(true));
+ if(editor.service != null){
+ editor.service.setOut(sketchOut);
+ editor.service.setErr(sketchErr);
+ }
+
startTimer();
}
diff --git a/app/src/processing/app/ui/PreferencesFrame.java b/app/src/processing/app/ui/PreferencesFrame.java
index a8cf68c27..28424a2ea 100644
--- a/app/src/processing/app/ui/PreferencesFrame.java
+++ b/app/src/processing/app/ui/PreferencesFrame.java
@@ -85,6 +85,7 @@ public class PreferencesFrame {
JCheckBox hidpiDisableBox;
// JLabel hidpiRestartLabel;
JCheckBox syncSketchNameBox;
+ JCheckBox useModernBuildSystem;
JComboBox displaySelectionBox;
JComboBox languageSelectionBox;
@@ -554,6 +555,9 @@ public class PreferencesFrame {
runningPanel.setBorder(new TitledBorder("Running"));
runningPanel.setLayout(new BoxLayout(runningPanel, BoxLayout.Y_AXIS));
+ useModernBuildSystem = new JCheckBox(Language.text("preferences.use_modern_build_system"));
+ addRow(runningPanel, useModernBuildSystem);
+
addRow(runningPanel, displayLabel, displaySelectionBox);
addRow(runningPanel, backgroundColorLabel, presentColor);
addRow(runningPanel, memoryOverrideBox, memoryField, mbLabel);
@@ -827,6 +831,8 @@ public class PreferencesFrame {
Preferences.setBoolean("pdex.completion", codeCompletionBox.isSelected());
Preferences.setBoolean("pdex.suggest.imports", importSuggestionsBox.isSelected());
+ Preferences.setBoolean("run.use_gradle", useModernBuildSystem.isSelected());
+
for (Editor editor : base.getEditors()) {
editor.applyPreferences();
}
@@ -902,6 +908,11 @@ public class PreferencesFrame {
if (autoAssociateBox != null) {
autoAssociateBox.setSelected(Preferences.getBoolean("platform.auto_file_type_associations")); //$NON-NLS-1$
}
+
+ if(useModernBuildSystem != null) {
+ useModernBuildSystem.setSelected(Preferences.getBoolean("run.use_gradle"));
+ }
+
// The OK Button has to be set as the default button every time the
// PrefWindow is to be displayed
frame.getRootPane().setDefaultButton(okButton);
diff --git a/app/test/processing/app/gradle/GradleServiceTest.kt b/app/test/processing/app/gradle/GradleServiceTest.kt
new file mode 100644
index 000000000..64a04d447
--- /dev/null
+++ b/app/test/processing/app/gradle/GradleServiceTest.kt
@@ -0,0 +1,13 @@
+package processing.app.gradle
+
+import org.junit.jupiter.api.Assertions.*
+import processing.app.ui.Editor
+import kotlin.test.Test
+import org.mockito.kotlin.mock
+
+class GradleServiceTest{
+
+ @Test
+ fun testRunningSketch(){
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 0675c2db3..faa68e5dc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,6 +6,21 @@ plugins {
alias(libs.plugins.jetbrainsCompose) apply false
}
+//allprojects{
+// repositories{
+// maven { url = uri("https://repo.gradle.org/gradle/libs-releases") }
+// }
+//}
+
// Set the build directory to not /build to prevent accidental deletion through the clean action
// Can be deleted after the migration to Gradle is complete
-layout.buildDirectory = file(".build")
\ No newline at end of file
+layout.buildDirectory = file(".build")
+
+allprojects{
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+}
\ No newline at end of file
diff --git a/build/shared/lib/defaults.txt b/build/shared/lib/defaults.txt
index 6e3e00f0d..431988bb7 100644
--- a/build/shared/lib/defaults.txt
+++ b/build/shared/lib/defaults.txt
@@ -186,6 +186,9 @@ console.temp.days = 7
console.scrollback.lines = 500
console.scrollback.chars = 40000
+# run java sketches with Gradle aka the Modern Build System
+run.use_gradle = false
+
# Any additional Java options when running.
# If you change this and can't run things, it's your own durn fault.
run.options =
diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties
index 9d03f33c0..3ea6d7652 100644
--- a/build/shared/lib/languages/PDE.properties
+++ b/build/shared/lib/languages/PDE.properties
@@ -621,6 +621,24 @@ update_check = Update
update_check.updates_available.core = A new version of Processing is available,\nwould you like to visit the Processing download page?
update_check.updates_available.contributions = There are updates available for some of the installed contributions,\nwould you like to open the the Contribution Manager now?
+
+# ---------------------------------------
+# Welcome
+welcome.intro.title = Welcome to Processing
+welcome.intro.message = A flexible software sketchbook and a language for learning how to code.
+welcome.intro.suggestion = Is it your first time using Processing? Try one of the examples on the right.
+welcome.action.examples = More examples
+welcome.action.tutorials = Tutorials
+welcome.action.startup = Show this window at startup
+welcome.action.go = Let's go!
+
+# ---------------------------------------
+# Beta
+beta.window.title = Welcome to Beta
+beta.title = Welcome to the Processing Beta
+beta.message = Thank you for trying out the new version of Processing. We're very grateful!\n\nPlease report any bugs on the forums.
+beta.button = Got it!
+
# ---------------------------------------
# Beta
beta.window.title = Welcome to Beta
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 8f7211b13..f646bc9b8 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -15,6 +15,7 @@ sourceSets{
main{
java{
srcDirs("src")
+ exclude("**/*.jnilib")
}
resources{
srcDirs("src")
@@ -34,10 +35,21 @@ dependencies {
testImplementation(libs.junit)
}
+publishing{
+ repositories{
+ maven {
+ name = "App"
+ url = uri(project(":app").layout.buildDirectory.dir("resources-bundled/common/repository").get().asFile.absolutePath)
+ }
+ }
+}
mavenPublishing{
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
- signAllPublications()
+
+ // Only sign if signing is set up
+ if(project.hasProperty("signing.keyId") || project.hasProperty("signing.signingInMemoryKey"))
+ signAllPublications()
pom{
name.set("Processing Core")
@@ -77,3 +89,6 @@ tasks.withType {
tasks.compileJava{
options.encoding = "UTF-8"
}
+tasks.javadoc{
+ options.encoding = "UTF-8"
+}
diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java
index d1297ec6f..5187ff5c0 100644
--- a/core/src/processing/core/PApplet.java
+++ b/core/src/processing/core/PApplet.java
@@ -705,7 +705,7 @@ public class PApplet implements PConstants {
protected boolean exitCalled;
// ok to be static because it's not possible to mix enabled/disabled
- static protected boolean disableAWT;
+ static protected boolean disableAWT = System.getProperty("processing.awt.disable", "false").equals("true");;
// messages to send if attached as an external vm
@@ -9932,19 +9932,21 @@ public class PApplet implements PConstants {
System.exit(1);
}
- boolean external = false;
- int[] location = null;
- int[] editorLocation = null;
+ boolean external = System.getProperty("processing.external", "false").equals("true");;
+ int[] location = System.getProperty("processing.location", null) != null ?
+ parseInt(split(System.getProperty("processing.location"), ',')) : null;
+ int[] editorLocation = System.getProperty("processing.editor.location", null) != null ?
+ parseInt(split(System.getProperty("processing.editor.location"), ',')) : null;
String name = null;
int windowColor = 0;
int stopColor = 0xff808080;
- boolean hideStop = false;
+ boolean hideStop = System.getProperty("processing.stop.hide", "false").equals("true");
int displayNum = -1; // use default
- boolean present = false;
- boolean fullScreen = false;
- float uiScale = 0;
+ boolean present = System.getProperty("processing.present", "false").equals("true");
+ boolean fullScreen = System.getProperty("processing.fullscreen", "false").equals("true");
+ float uiScale = parseInt(System.getProperty("processing.uiScale", "0"), 0);
String param, value;
String folder = calcSketchPath();
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 70f93aaff..1de974f21 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,6 +2,7 @@
kotlin = "2.0.20"
compose-plugin = "1.7.1"
jogl = "2.5.0"
+antlr = "4.13.2"
jupiter = "5.12.0"
[libraries]
@@ -25,6 +26,11 @@ netbeansSwing = { module = "org.netbeans.api:org-netbeans-swing-outline", versio
ant = { module = "org.apache.ant:ant", version = "1.10.14" }
lsp4j = { module = "org.eclipse.lsp4j:org.eclipse.lsp4j", version = "0.22.0" }
jsoup = { module = "org.jsoup:jsoup", version = "1.17.2" }
+antlr4 = { module = "org.antlr:antlr4", version.ref = "antlr" }
+antlr4Runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr" }
+composeGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-plugin" }
+kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+kotlinComposePlugin = { module = "org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin", version.ref = "kotlin" }
markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version = "0.31.0" }
markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" }
clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" }
@@ -35,4 +41,5 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref =
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
download = { id = "de.undercouch.download", version = "5.6.0" }
-mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
\ No newline at end of file
+mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
+gradlePublish = { id = "com.gradle.plugin-publish", version = "1.2.1" }
\ No newline at end of file
diff --git a/java/build.gradle.kts b/java/build.gradle.kts
index 0f8e05278..74052f208 100644
--- a/java/build.gradle.kts
+++ b/java/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
plugins {
id("java")
}
@@ -53,7 +55,7 @@ tasks.register("extraResources"){
include("keywords.txt")
include("theme/**/*")
include("application/**/*")
- into( layout.buildDirectory.dir("resources-bundled/common/modes/java"))
+ into(layout.buildDirectory.dir("resources-bundled/common/modes/java"))
}
tasks.register("copyCore"){
val coreProject = project(":core")
@@ -64,6 +66,22 @@ tasks.register("copyCore"){
rename("core.+\\.jar", "core.jar")
into(coreProject.layout.projectDirectory.dir("library"))
}
+tasks.register("renameWindres") {
+ val dir = layout.buildDirectory.dir("resources-bundled/common/modes/java")
+ val os = DefaultNativePlatform.getCurrentOperatingSystem()
+ val platform = when {
+ os.isWindows -> "windows"
+ os.isMacOsX -> "macos"
+ else -> "linux"
+ }
+ from(dir) {
+ include("*-$platform*")
+ rename("(.*)-$platform(.*)", "$1$2")
+ }
+ duplicatesStrategy = DuplicatesStrategy.INCLUDE
+ into(dir)
+ tasks.named("extraResources"){ dependsOn(this) }
+}
val libraries = arrayOf("dxf","io","net","pdf","serial","svg")
libraries.forEach { library ->
@@ -77,7 +95,7 @@ libraries.forEach { library ->
include("*.properties")
include("library/**/*")
include("examples/**/*")
- into( layout.buildDirectory.dir("resources-bundled/common/modes/java/libraries/$library"))
+ into(layout.buildDirectory.dir("resources-bundled/common/modes/java/libraries/$library"))
}
tasks.named("extraResources"){ dependsOn("library-$library-extraResources") }
}
diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts
new file mode 100644
index 000000000..3aa484cb5
--- /dev/null
+++ b/java/gradle/build.gradle.kts
@@ -0,0 +1,38 @@
+plugins{
+ `java-gradle-plugin`
+ alias(libs.plugins.gradlePublish)
+
+ kotlin("jvm") version libs.versions.kotlin
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies{
+ implementation(project(":java:preprocessor"))
+
+ implementation(libs.composeGradlePlugin)
+ implementation(libs.kotlinGradlePlugin)
+ implementation(libs.kotlinComposePlugin)
+
+ testImplementation(libs.junit)
+}
+
+gradlePlugin{
+ plugins{
+ create("processing"){
+ id = "org.processing.gradle"
+ implementationClass = "org.processing.java.gradle.ProcessingPlugin"
+ }
+ }
+}
+publishing{
+ repositories{
+ mavenLocal()
+ maven {
+ name = "App"
+ url = uri(project(":app").layout.buildDirectory.dir("resources-bundled/common/repository").get().asFile.absolutePath)
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/example/.idea/.gitignore b/java/gradle/example/.idea/.gitignore
new file mode 100644
index 000000000..a0ccf77bc
--- /dev/null
+++ b/java/gradle/example/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Environment-dependent path to Maven home directory
+/mavenHomeManager.xml
diff --git a/java/gradle/example/.idea/gradle.xml b/java/gradle/example/.idea/gradle.xml
new file mode 100644
index 000000000..11bd2cc4c
--- /dev/null
+++ b/java/gradle/example/.idea/gradle.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/kotlinc.xml b/java/gradle/example/.idea/kotlinc.xml
new file mode 100644
index 000000000..d4b7accba
--- /dev/null
+++ b/java/gradle/example/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/misc.xml b/java/gradle/example/.idea/misc.xml
new file mode 100644
index 000000000..6ed36dd36
--- /dev/null
+++ b/java/gradle/example/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/vcs.xml b/java/gradle/example/.idea/vcs.xml
new file mode 100644
index 000000000..c2365ab11
--- /dev/null
+++ b/java/gradle/example/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/brightness.pde b/java/gradle/example/brightness.pde
new file mode 100644
index 000000000..1da8c36ac
--- /dev/null
+++ b/java/gradle/example/brightness.pde
@@ -0,0 +1,32 @@
+/**
+ * Brightness
+ * by Rusty Robison.
+ *
+ * Brightness is the relative lightness or darkness of a color.
+ * Move the cursor vertically over each bar to alter its brightness.
+ */
+
+int barWidth = 20;
+int lastBar = -1;
+
+import controlP5.*;
+
+ControlP5 cp5;
+
+
+void setup() {
+ size(640, 360, P2D);
+ colorMode(HSB, width, 100, height);
+ noStroke();
+ background(0);
+}
+
+void draw() {
+ int whichBar = mouseX / barWidth;
+ if (whichBar != lastBar) {
+ int barX = whichBar * barWidth;
+ fill(barX, 100, mouseY);
+ rect(barX, 0, barWidth, height);
+ lastBar = whichBar;
+ }
+}
diff --git a/java/gradle/example/build.gradle.kts b/java/gradle/example/build.gradle.kts
new file mode 100644
index 000000000..4d1d983e5
--- /dev/null
+++ b/java/gradle/example/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins{
+ id("org.processing.gradle")
+}
\ No newline at end of file
diff --git a/java/gradle/example/settings.gradle.kts b/java/gradle/example/settings.gradle.kts
new file mode 100644
index 000000000..ee9c97e15
--- /dev/null
+++ b/java/gradle/example/settings.gradle.kts
@@ -0,0 +1,5 @@
+rootProject.name = "processing-gradle-plugin-demo"
+
+pluginManagement {
+ includeBuild("../../../")
+}
\ No newline at end of file
diff --git a/java/gradle/src/main/kotlin/DependenciesTask.kt b/java/gradle/src/main/kotlin/DependenciesTask.kt
new file mode 100644
index 000000000..320f1ebb0
--- /dev/null
+++ b/java/gradle/src/main/kotlin/DependenciesTask.kt
@@ -0,0 +1,76 @@
+package org.processing.java.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.ObjectInputStream
+
+abstract class DependenciesTask: DefaultTask() {
+ @InputFile
+ val librariesMetaData: RegularFileProperty = project.objects.fileProperty()
+
+ @InputFile
+ val sketchMetaData: RegularFileProperty = project.objects.fileProperty()
+
+ init{
+ librariesMetaData.convention(project.layout.buildDirectory.file("processing/libraries"))
+ sketchMetaData.convention(project.layout.buildDirectory.file("processing/sketch"))
+ }
+
+ @TaskAction
+ fun execute() {
+ val sketchMetaFile = sketchMetaData.get().asFile
+ val librariesMetaFile = librariesMetaData.get().asFile
+
+ val libraries = librariesMetaFile.inputStream().use { input ->
+ ObjectInputStream(input).readObject() as ArrayList
+ }
+
+ val sketch = sketchMetaFile.inputStream().use { input ->
+ ObjectInputStream(input).readObject() as PDETask.SketchMeta
+ }
+
+ val dependencies = mutableSetOf()
+
+ // Loop over the import statements in the sketch and import the relevant jars from the libraries
+ sketch.importStatements.forEach import@{ statement ->
+ libraries.forEach { library ->
+ library.jars.forEach { jar ->
+ jar.classes.forEach { className ->
+ if (className.startsWith(statement)) {
+ dependencies.add(jar.path)
+ return@import
+ }
+ }
+ }
+ }
+ }
+ project.dependencies.add("implementation", project.files(dependencies) )
+
+ // TODO: Add only if user is compiling for P2D or P3D
+ // Add JOGL and Gluegen dependencies
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:jogl-all-main:2.5.0")
+ project.dependencies.add("runtimeOnly", "org.jogamp.gluegen:gluegen-rt-main:2.5.0")
+
+ // TODO: Only add the native dependencies for the platform the user is building for
+ // MacOS specific native dependencies
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:jogl-all:2.5.0:natives-macosx-universal")
+ project.dependencies.add("runtimeOnly", "org.jogamp.gluegen:gluegen-rt:2.5.0:natives-macosx-universal")
+
+ // Windows specific native dependencies
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:jogl-all:2.5.0:natives-windows-amd64")
+ project.dependencies.add("runtimeOnly", "org.jogamp.gluegen:gluegen-rt:2.5.0:natives-windows-amd64")
+
+ // Linux specific native dependencies
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:jogl-all:2.5.0:natives-linux-amd64")
+ project.dependencies.add("runtimeOnly", "org.jogamp.gluegen:gluegen-rt:2.5.0:natives-linux-amd64")
+
+ // NativeWindow dependencies for all platforms
+ project.dependencies.add("implementation", "org.jogamp.jogl:nativewindow:2.5.0")
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:nativewindow:2.5.0:natives-macosx-universal")
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:nativewindow:2.5.0:natives-windows-amd64")
+ project.dependencies.add("runtimeOnly", "org.jogamp.jogl:nativewindow:2.5.0:natives-linux-amd64")
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/src/main/kotlin/LibrariesTask.kt b/java/gradle/src/main/kotlin/LibrariesTask.kt
new file mode 100644
index 000000000..1ac0dd51e
--- /dev/null
+++ b/java/gradle/src/main/kotlin/LibrariesTask.kt
@@ -0,0 +1,71 @@
+package org.processing.java.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.ObjectOutputStream
+import java.util.jar.JarFile
+
+abstract class LibrariesTask : DefaultTask() {
+
+ // TODO: Allow this directory to not exist
+ @InputDirectory
+ val librariesDirectory: DirectoryProperty = project.objects.directoryProperty()
+
+ @OutputFile
+ val librariesMetaData: RegularFileProperty = project.objects.fileProperty()
+
+ init{
+ librariesMetaData.convention(project.layout.buildDirectory.file("processing/libraries"))
+ }
+
+ data class Jar(
+ val path: File,
+ val classes: List
+ ) : java.io.Serializable
+
+ data class Library(
+ val jars: List
+ ) : java.io.Serializable
+
+ @TaskAction
+ fun execute() {
+ val libraries = librariesDirectory.get().asFile
+ .listFiles { file -> file.isDirectory }
+ ?.map { folder ->
+ // Find all the jars in the sketchbook
+ val jars = folder.resolve("library")
+ .listFiles{ file -> file.extension == "jar" }
+ ?.map{ file ->
+
+ // Inside of each jar, look for the defined classes
+ val jar = JarFile(file)
+ val classes = jar.entries().asSequence()
+ .filter { entry -> entry.name.endsWith(".class") }
+ .map { entry -> entry.name }
+ .map { it.substringBeforeLast('/').replace('/', '.') }
+ .distinct()
+ .toList()
+
+ // Return a reference to the jar and its classes
+ return@map Jar(
+ path = file,
+ classes = classes
+ )
+ }?: emptyList()
+
+ // Save the parsed jars and which folder
+ return@map Library(
+ jars = jars
+ )
+ }?: emptyList()
+
+ val meta = ObjectOutputStream(librariesMetaData.get().asFile.outputStream())
+ meta.writeObject(libraries)
+ meta.close()
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/src/main/kotlin/PDETask.kt b/java/gradle/src/main/kotlin/PDETask.kt
new file mode 100644
index 000000000..22e9360ac
--- /dev/null
+++ b/java/gradle/src/main/kotlin/PDETask.kt
@@ -0,0 +1,95 @@
+package org.processing.java.gradle
+
+import org.gradle.api.file.*
+import org.gradle.api.tasks.*
+import org.gradle.internal.file.Deleter
+import org.gradle.work.InputChanges
+import processing.mode.java.preproc.PdePreprocessor
+import java.io.File
+import java.io.ObjectOutputStream
+import java.util.concurrent.Callable
+import java.util.jar.JarFile
+import javax.inject.Inject
+
+
+// TODO: Generate sourcemaps
+abstract class PDETask : SourceTask() {
+
+
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ @get:IgnoreEmptyDirectories
+ @get:SkipWhenEmpty
+ open val stableSources: FileCollection = project.files(Callable { this.source })
+
+ @OutputDirectory
+ val outputDirectory = project.objects.directoryProperty()
+
+ @get:Input
+ @get:Optional
+ var workingDir: String? = null
+
+ @get:Input
+ var sketchName: String = "processing"
+
+ @get:Input
+ @get:Optional
+ var sketchBook: String? = null
+
+ @OutputFile
+ val sketchMetaData = project.objects.fileProperty()
+
+ init{
+ outputDirectory.convention(project.layout.buildDirectory.dir("generated/pde"))
+ sketchMetaData.convention(project.layout.buildDirectory.file("processing/sketch"))
+ }
+
+ data class SketchMeta(
+ val sketchName: String,
+ val sketchRenderer: String?,
+ val importStatements: List
+ ) : java.io.Serializable
+
+ @TaskAction
+ fun execute() {
+ // TODO: Allow pre-processor to run on individual files (future)
+ // TODO: Only compare file names from both defined roots (e.g. sketch.pde and folder/sketch.pde should both be included)
+
+ // Using stableSources since we can only run the pre-processor on the full set of sources
+ val combined = stableSources
+ .files
+ .groupBy { it.name }
+ .map { entry ->
+ entry.value.maxByOrNull { it.lastModified() }!!
+ }
+ .joinToString("\n"){
+ it.readText()
+ }
+ val javaFile = File(outputDirectory.get().asFile, "$sketchName.java").bufferedWriter()
+
+ val meta = PdePreprocessor
+ .builderFor(sketchName)
+ .setTabSize(4)
+ .build()
+ .write(javaFile, combined)
+
+ javaFile.flush()
+ javaFile.close()
+
+ val sketchMeta = SketchMeta(
+ sketchName = sketchName,
+ sketchRenderer = meta.sketchRenderer,
+ importStatements = meta.importStatements.map { importStatement -> importStatement.packageName }
+ )
+
+ val metaFile = ObjectOutputStream(sketchMetaData.get().asFile.outputStream())
+ metaFile.writeObject(sketchMeta)
+ metaFile.close()
+ }
+
+ @get:Inject
+ open val deleter: Deleter
+ get() {
+ throw UnsupportedOperationException("Decorator takes care of injection")
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt
new file mode 100644
index 000000000..472365209
--- /dev/null
+++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt
@@ -0,0 +1,183 @@
+package org.processing.java.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.file.DefaultSourceDirectorySet
+import org.gradle.api.internal.tasks.TaskDependencyFactory
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.api.tasks.JavaExec
+import org.jetbrains.compose.ComposeExtension
+import org.jetbrains.compose.desktop.DesktopExtension
+import processing.app.Preferences
+import java.io.File
+import java.util.*
+import javax.inject.Inject
+
+// TODO: CI/CD for publishing the plugin
+class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFactory) : Plugin {
+ override fun apply(project: Project) {
+ val sketchName = project.layout.projectDirectory.asFile.name.replace(Regex("[^a-zA-Z0-9_]"), "_")
+
+ val isProcessing = project.findProperty("processing.version") != null
+ val processingVersion = project.findProperty("processing.version") as String? ?: "4.3.4"
+ val processingGroup = project.findProperty("processing.group") as String? ?: "org.processing"
+ val workingDir = project.findProperty("processing.workingDir") as String?
+ val debugPort = project.findProperty("processing.debugPort") as String?
+
+ val sketchbook = project.findProperty("processing.sketchbook") as String?
+
+ // Apply the Java plugin to the Project
+ project.plugins.apply(JavaPlugin::class.java)
+
+ if(isProcessing){
+ // Set the build directory to a temp file so it doesn't clutter up the sketch folder
+ // Only if the build directory doesn't exist, otherwise proceed as normal
+ if(!project.layout.buildDirectory.asFile.get().exists()) {
+ project.layout.buildDirectory.set(File(project.findProperty("processing.workingDir") as String))
+ }
+ // Disable the wrapper in the sketch to keep it cleaner
+ project.tasks.findByName("wrapper")?.enabled = false
+ }
+
+ // Add the compose plugin to wrap the sketch in an executable
+ project.plugins.apply("org.jetbrains.compose")
+
+ // Add kotlin support
+ project.plugins.apply("org.jetbrains.kotlin.jvm")
+ // Add jetpack compose support
+ project.plugins.apply("org.jetbrains.kotlin.plugin.compose")
+
+ // Add the Processing core library (within Processing from the internal maven repo and outside from the internet)
+ project.dependencies.add("implementation", "$processingGroup:core:${processingVersion}")
+
+ // Add the jars in the code folder
+ project.dependencies.add("implementation", project.fileTree("src").apply { include("**/code/*.jar") })
+
+ // Add the repositories necessary for building the sketch
+ project.repositories.add(project.repositories.maven { it.setUrl("https://jogamp.org/deployment/maven") })
+ project.repositories.add(project.repositories.mavenCentral())
+ project.repositories.add(project.repositories.mavenLocal())
+
+ // Configure the compose Plugin
+ project.extensions.configure(ComposeExtension::class.java) { extension ->
+ extension.extensions.getByType(DesktopExtension::class.java).application { application ->
+ // Set the class to be executed initially
+ application.mainClass = sketchName
+ application.nativeDistributions.modules("java.management")
+ if(debugPort != null) {
+ application.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=$debugPort")
+ }
+ }
+ }
+
+ // TODO: Add support for customizing distributables
+ // TODO: Setup sensible defaults for the distributables
+
+ // Add convenience tasks for running, presenting, and exporting the sketch outside of Processing
+ if(!isProcessing) {
+ project.tasks.create("sketch").apply {
+ group = "processing"
+ description = "Runs the Processing sketch"
+ dependsOn("run")
+ }
+ project.tasks.create("present").apply {
+ group = "processing"
+ description = "Presents the Processing sketch"
+ doFirst {
+ project.tasks.withType(JavaExec::class.java).configureEach { task ->
+ task.systemProperty("processing.fullscreen", "true")
+ }
+ }
+ finalizedBy("run")
+ }
+ project.tasks.create("export").apply {
+ group = "processing"
+ description = "Creates a distributable version of the Processing sketch"
+
+ dependsOn("createDistributable")
+
+ }
+ }
+
+ project.afterEvaluate {
+ // Copy the result of create distributable to the project directory
+ project.tasks.named("createDistributable") { task ->
+ task.doLast {
+ project.copy {
+ it.from(project.tasks.named("createDistributable").get().outputs.files)
+ it.into(project.layout.projectDirectory)
+ }
+ }
+ }
+ }
+
+ // Move the processing variables into javaexec tasks so they can be used in the sketch as well
+ project.tasks.withType(JavaExec::class.java).configureEach { task ->
+ project.properties
+ .filterKeys { it.startsWith("processing") }
+ .forEach { (key, value) -> task.systemProperty(key, value) }
+ }
+
+ project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.all { sourceSet ->
+ // For each java source set (mostly main) add a new source set for the PDE files
+ val pdeSourceSet = objectFactory.newInstance(
+ DefaultPDESourceDirectorySet::class.java,
+ objectFactory.sourceDirectorySet("${sourceSet.name}.pde", "${sourceSet.name} Processing Source")
+ ).apply {
+ filter.include("**/*.pde")
+ filter.exclude("${project.layout.buildDirectory.asFile.get().name}/**")
+
+ srcDir("./")
+ srcDir("$workingDir/unsaved")
+ }
+ sourceSet.allSource.source(pdeSourceSet)
+
+ val librariesTaskName = sourceSet.getTaskName("scanLibraries", "PDE")
+ val librariesScan = project.tasks.register(librariesTaskName, LibrariesTask::class.java) { task ->
+ task.description = "Scans the libraries in the sketchbook"
+ task.librariesDirectory.set(File(sketchbook, "libraries"))
+ }
+
+ val pdeTaskName = sourceSet.getTaskName("preprocess", "PDE")
+ val pdeTask = project.tasks.register(pdeTaskName, PDETask::class.java) { task ->
+ task.description = "Processes the ${sourceSet.name} PDE"
+ task.source = pdeSourceSet
+ task.sketchName = sketchName
+ task.workingDir = workingDir
+ task.sketchBook = sketchbook
+
+ // Set the output of the pre-processor as the input for the java compiler
+ sourceSet.java.srcDir(task.outputDirectory)
+
+ task.doLast {
+ // Copy java files from the root to the generated directory
+ project.copy { copyTask ->
+ copyTask.from(project.layout.projectDirectory){ from ->
+ from.include("*.java")
+ }
+ copyTask.into(task.outputDirectory)
+ }
+ }
+ }
+
+ val depsTaskName = sourceSet.getTaskName("addLegacyDependencies", "PDE")
+ project.tasks.register(depsTaskName, DependenciesTask::class.java){ task ->
+ task.dependsOn(pdeTask, librariesScan)
+ }
+
+ project.tasks.named(
+ sourceSet.compileJavaTaskName
+ ) { task ->
+ task.dependsOn(pdeTaskName, depsTaskName)
+ }
+ }
+ }
+ abstract class DefaultPDESourceDirectorySet @Inject constructor(
+ sourceDirectorySet: SourceDirectorySet,
+ taskDependencyFactory: TaskDependencyFactory
+ ) : DefaultSourceDirectorySet(sourceDirectorySet, taskDependencyFactory), SourceDirectorySet
+}
+
diff --git a/java/gradle/src/test/kotlin/ProcessingPluginTest.kt b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt
new file mode 100644
index 000000000..2be09fd89
--- /dev/null
+++ b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt
@@ -0,0 +1,18 @@
+import org.gradle.api.Task
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class ProcessingPluginTest{
+ // TODO: Write tests
+ // TODO: Test on multiple platforms since there is meaningful differences between the platforms
+ @Test
+ fun testPluginAddsSketchTask(){
+ val project = ProjectBuilder.builder().build()
+ project.pluginManager.apply("org.processing.gradle")
+
+ assert(project.tasks.getByName("sketch") is Task)
+ }
+}
diff --git a/java/lsp/build.gradle.kts b/java/lsp/build.gradle.kts
index 63ac7ab72..393f30344 100644
--- a/java/lsp/build.gradle.kts
+++ b/java/lsp/build.gradle.kts
@@ -33,7 +33,10 @@ dependencies{
mavenPublishing{
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
- signAllPublications()
+
+ // Only sign if signing is set up
+ if(project.hasProperty("signing.keyId") || project.hasProperty("signing.signingInMemoryKey"))
+ signAllPublications()
pom{
name.set("Processing Language Server")
diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts
index c6855df06..e902afe21 100644
--- a/java/preprocessor/build.gradle.kts
+++ b/java/preprocessor/build.gradle.kts
@@ -1,7 +1,8 @@
import com.vanniktech.maven.publish.SonatypeHost
plugins{
- id("java")
+ java
+ antlr
alias(libs.plugins.mavenPublish)
}
@@ -14,23 +15,41 @@ repositories{
sourceSets{
main{
java{
- srcDirs("src/main/java", "../src/", "../generated/")
+ srcDirs("src/main/java", "../src/")
include("processing/mode/java/preproc/**/*", "processing/app/**/*")
}
}
-
+}
+afterEvaluate{
+ tasks.withType(Jar::class.java){
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ dependsOn(tasks.generateGrammarSource)
+ }
}
dependencies{
implementation(libs.antlr)
implementation(libs.eclipseJDT)
- implementation(project(":core"))
+ antlr(libs.antlr4)
+ implementation(libs.antlr4Runtime)
+}
+
+publishing{
+ repositories{
+ maven {
+ name = "App"
+ url = uri(project(":app").layout.buildDirectory.dir("resources-bundled/common/repository").get().asFile.absolutePath)
+ }
+ }
}
mavenPublishing{
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
- signAllPublications()
+
+ // Only sign if signing is set up
+ if(project.hasProperty("signing.keyId") || project.hasProperty("signing.signingInMemoryKey"))
+ signAllPublications()
pom{
name.set("Processing Pre-processor")
@@ -58,13 +77,4 @@ mavenPublishing{
developerConnection.set("scm:git:ssh://git@github.com/processing/processing4.git")
}
}
-}
-tasks.withType {
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-}
-tasks.compileJava{
- dependsOn("ant-preproc")
-}
-ant.importBuild("../build.xml"){ antTaskName ->
- "ant-$antTaskName"
}
\ No newline at end of file
diff --git a/java/preprocessor/src/main/antlr/JavaLexer.g4 b/java/preprocessor/src/main/antlr/JavaLexer.g4
new file mode 100644
index 000000000..b924864ea
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/JavaLexer.g4
@@ -0,0 +1,235 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8)
+ Copyright (c) 2021 MichaΕ Lorek (upgrade to Java 11)
+ Copyright (c) 2022 MichaΕ Lorek (upgrade to Java 17)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false
+// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine
+// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true
+
+lexer grammar JavaLexer;
+
+// Keywords
+
+ABSTRACT : 'abstract';
+ASSERT : 'assert';
+BOOLEAN : 'boolean';
+BREAK : 'break';
+BYTE : 'byte';
+CASE : 'case';
+CATCH : 'catch';
+CHAR : 'char';
+CLASS : 'class';
+CONST : 'const';
+CONTINUE : 'continue';
+DEFAULT : 'default';
+DO : 'do';
+DOUBLE : 'double';
+ELSE : 'else';
+ENUM : 'enum';
+EXTENDS : 'extends';
+FINAL : 'final';
+FINALLY : 'finally';
+FLOAT : 'float';
+FOR : 'for';
+IF : 'if';
+GOTO : 'goto';
+IMPLEMENTS : 'implements';
+IMPORT : 'import';
+INSTANCEOF : 'instanceof';
+INT : 'int';
+INTERFACE : 'interface';
+LONG : 'long';
+NATIVE : 'native';
+NEW : 'new';
+PACKAGE : 'package';
+PRIVATE : 'private';
+PROTECTED : 'protected';
+PUBLIC : 'public';
+RETURN : 'return';
+SHORT : 'short';
+STATIC : 'static';
+STRICTFP : 'strictfp';
+SUPER : 'super';
+SWITCH : 'switch';
+SYNCHRONIZED : 'synchronized';
+THIS : 'this';
+THROW : 'throw';
+THROWS : 'throws';
+TRANSIENT : 'transient';
+TRY : 'try';
+VOID : 'void';
+VOLATILE : 'volatile';
+WHILE : 'while';
+
+// Module related keywords
+MODULE : 'module';
+OPEN : 'open';
+REQUIRES : 'requires';
+EXPORTS : 'exports';
+OPENS : 'opens';
+TO : 'to';
+USES : 'uses';
+PROVIDES : 'provides';
+WITH : 'with';
+TRANSITIVE : 'transitive';
+
+// Local Variable Type Inference
+VAR: 'var'; // reserved type name
+
+// Switch Expressions
+YIELD: 'yield'; // reserved type name from Java 14
+
+// Records
+RECORD: 'record';
+
+// Sealed Classes
+SEALED : 'sealed';
+PERMITS : 'permits';
+NON_SEALED : 'non-sealed';
+
+// Literals
+
+DECIMAL_LITERAL : ('0' | [1-9] (Digits? | '_'+ Digits)) [lL]?;
+HEX_LITERAL : '0' [xX] [0-9a-fA-F] ([0-9a-fA-F_]* [0-9a-fA-F])? [lL]?;
+OCT_LITERAL : '0' '_'* [0-7] ([0-7_]* [0-7])? [lL]?;
+BINARY_LITERAL : '0' [bB] [01] ([01_]* [01])? [lL]?;
+
+FLOAT_LITERAL:
+ (Digits '.' Digits? | '.' Digits) ExponentPart? [fFdD]?
+ | Digits (ExponentPart [fFdD]? | [fFdD])
+;
+
+HEX_FLOAT_LITERAL: '0' [xX] (HexDigits '.'? | HexDigits? '.' HexDigits) [pP] [+-]? Digits [fFdD]?;
+
+BOOL_LITERAL: 'true' | 'false';
+
+CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence) '\'';
+
+STRING_LITERAL: '"' (~["\\\r\n] | EscapeSequence)* '"';
+
+MULTI_STRING_LIT: '"""' (~[\\] | EscapeSequence)*? '"""';
+
+TEXT_BLOCK: '"""' [ \t]* [\r\n] (. | EscapeSequence)*? '"""';
+
+NULL_LITERAL: 'null';
+
+// Separators
+
+LPAREN : '(';
+RPAREN : ')';
+LBRACE : '{';
+RBRACE : '}';
+LBRACK : '[';
+RBRACK : ']';
+SEMI : ';';
+COMMA : ',';
+DOT : '.';
+
+// Operators
+
+ASSIGN : '=';
+GT : '>';
+LT : '<';
+BANG : '!';
+TILDE : '~';
+QUESTION : '?';
+COLON : ':';
+EQUAL : '==';
+LE : '<=';
+GE : '>=';
+NOTEQUAL : '!=';
+AND : '&&';
+OR : '||';
+INC : '++';
+DEC : '--';
+ADD : '+';
+SUB : '-';
+MUL : '*';
+DIV : '/';
+BITAND : '&';
+BITOR : '|';
+CARET : '^';
+MOD : '%';
+
+ADD_ASSIGN : '+=';
+SUB_ASSIGN : '-=';
+MUL_ASSIGN : '*=';
+DIV_ASSIGN : '/=';
+AND_ASSIGN : '&=';
+OR_ASSIGN : '|=';
+XOR_ASSIGN : '^=';
+MOD_ASSIGN : '%=';
+LSHIFT_ASSIGN : '<<=';
+RSHIFT_ASSIGN : '>>=';
+URSHIFT_ASSIGN : '>>>=';
+
+// Java 8 tokens
+
+ARROW : '->';
+COLONCOLON : '::';
+
+// Additional symbols not defined in the lexical specification
+
+AT : '@';
+ELLIPSIS : '...';
+
+// Whitespace and comments
+
+WS : [ \t\r\n\u000C]+ -> channel(HIDDEN);
+COMMENT : '/*' .*? '*/' -> channel(HIDDEN);
+LINE_COMMENT : '//' ~[\r\n]* -> channel(HIDDEN);
+
+// Identifiers
+
+IDENTIFIER: Letter LetterOrDigit*;
+
+// Fragment rules
+
+fragment ExponentPart: [eE] [+-]? Digits;
+
+fragment EscapeSequence:
+ '\\' 'u005c'? [btnfr"'\\]
+ | '\\' 'u005c'? ([0-3]? [0-7])? [0-7]
+ | '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit
+;
+
+fragment HexDigits: HexDigit ((HexDigit | '_')* HexDigit)?;
+
+fragment HexDigit: [0-9a-fA-F];
+
+fragment Digits: [0-9] ([0-9_]* [0-9])?;
+
+fragment LetterOrDigit: Letter | [0-9];
+
+fragment Letter:
+ [a-zA-Z$_] // these are the "java letters" below 0x7F
+ | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate
+ | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
+;
\ No newline at end of file
diff --git a/java/preprocessor/src/main/antlr/JavaParser.g4 b/java/preprocessor/src/main/antlr/JavaParser.g4
new file mode 100644
index 000000000..d273fa888
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/JavaParser.g4
@@ -0,0 +1,826 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8)
+ Copyright (c) 2021 MichaΕ Lorek (upgrade to Java 11)
+ Copyright (c) 2022 MichaΕ Lorek (upgrade to Java 17)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false
+// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging
+
+parser grammar JavaParser;
+
+options {
+ tokenVocab = JavaLexer;
+}
+
+compilationUnit
+ : packageDeclaration? (importDeclaration | ';')* (typeDeclaration | ';')* EOF
+ | moduleDeclaration EOF
+ ;
+
+packageDeclaration
+ : annotation* PACKAGE qualifiedName ';'
+ ;
+
+importDeclaration
+ : IMPORT STATIC? qualifiedName ('.' '*')? ';'
+ ;
+
+typeDeclaration
+ : classOrInterfaceModifier* (
+ classDeclaration
+ | enumDeclaration
+ | interfaceDeclaration
+ | annotationTypeDeclaration
+ | recordDeclaration
+ )
+ ;
+
+modifier
+ : classOrInterfaceModifier
+ | NATIVE
+ | SYNCHRONIZED
+ | TRANSIENT
+ | VOLATILE
+ ;
+
+classOrInterfaceModifier
+ : annotation
+ | PUBLIC
+ | PROTECTED
+ | PRIVATE
+ | STATIC
+ | ABSTRACT
+ | FINAL // FINAL for class only -- does not apply to interfaces
+ | STRICTFP
+ | SEALED // Java17
+ | NON_SEALED // Java17
+ ;
+
+variableModifier
+ : FINAL
+ | annotation
+ ;
+
+classDeclaration
+ : CLASS identifier typeParameters? (EXTENDS typeType)? (IMPLEMENTS typeList)? (
+ PERMITS typeList
+ )? // Java17
+ classBody
+ ;
+
+typeParameters
+ : '<' typeParameter (',' typeParameter)* '>'
+ ;
+
+typeParameter
+ : annotation* identifier (EXTENDS annotation* typeBound)?
+ ;
+
+typeBound
+ : typeType ('&' typeType)*
+ ;
+
+enumDeclaration
+ : ENUM identifier (IMPLEMENTS typeList)? '{' enumConstants? ','? enumBodyDeclarations? '}'
+ ;
+
+enumConstants
+ : enumConstant (',' enumConstant)*
+ ;
+
+enumConstant
+ : annotation* identifier arguments? classBody?
+ ;
+
+enumBodyDeclarations
+ : ';' classBodyDeclaration*
+ ;
+
+interfaceDeclaration
+ : INTERFACE identifier typeParameters? (EXTENDS typeList)? (PERMITS typeList)? interfaceBody
+ ;
+
+classBody
+ : '{' classBodyDeclaration* '}'
+ ;
+
+interfaceBody
+ : '{' interfaceBodyDeclaration* '}'
+ ;
+
+classBodyDeclaration
+ : ';'
+ | STATIC? block
+ | modifier* memberDeclaration
+ ;
+
+memberDeclaration
+ : recordDeclaration //Java17
+ | methodDeclaration
+ | genericMethodDeclaration
+ | fieldDeclaration
+ | constructorDeclaration
+ | genericConstructorDeclaration
+ | interfaceDeclaration
+ | annotationTypeDeclaration
+ | classDeclaration
+ | enumDeclaration
+ ;
+
+/* We use rule this even for void methods which cannot have [] after parameters.
+ This simplifies grammar and we can consider void to be a type, which
+ renders the [] matching as a context-sensitive issue or a semantic check
+ for invalid return type after parsing.
+ */
+methodDeclaration
+ : typeTypeOrVoid identifier formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody
+ ;
+
+methodBody
+ : block
+ | ';'
+ ;
+
+typeTypeOrVoid
+ : typeType
+ | VOID
+ ;
+
+genericMethodDeclaration
+ : typeParameters methodDeclaration
+ ;
+
+genericConstructorDeclaration
+ : typeParameters constructorDeclaration
+ ;
+
+constructorDeclaration
+ : identifier formalParameters (THROWS qualifiedNameList)? constructorBody = block
+ ;
+
+compactConstructorDeclaration
+ : modifier* identifier constructorBody = block
+ ;
+
+fieldDeclaration
+ : typeType variableDeclarators ';'
+ ;
+
+interfaceBodyDeclaration
+ : modifier* interfaceMemberDeclaration
+ | ';'
+ ;
+
+interfaceMemberDeclaration
+ : recordDeclaration // Java17
+ | constDeclaration
+ | interfaceMethodDeclaration
+ | genericInterfaceMethodDeclaration
+ | interfaceDeclaration
+ | annotationTypeDeclaration
+ | classDeclaration
+ | enumDeclaration
+ ;
+
+constDeclaration
+ : typeType constantDeclarator (',' constantDeclarator)* ';'
+ ;
+
+constantDeclarator
+ : identifier ('[' ']')* '=' variableInitializer
+ ;
+
+// Early versions of Java allows brackets after the method name, eg.
+// public int[] return2DArray() [] { ... }
+// is the same as
+// public int[][] return2DArray() { ... }
+interfaceMethodDeclaration
+ : interfaceMethodModifier* interfaceCommonBodyDeclaration
+ ;
+
+// Java8
+interfaceMethodModifier
+ : annotation
+ | PUBLIC
+ | ABSTRACT
+ | DEFAULT
+ | STATIC
+ | STRICTFP
+ ;
+
+genericInterfaceMethodDeclaration
+ : interfaceMethodModifier* typeParameters interfaceCommonBodyDeclaration
+ ;
+
+interfaceCommonBodyDeclaration
+ : annotation* typeTypeOrVoid identifier formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody
+ ;
+
+variableDeclarators
+ : variableDeclarator (',' variableDeclarator)*
+ ;
+
+variableDeclarator
+ : variableDeclaratorId ('=' variableInitializer)?
+ ;
+
+variableDeclaratorId
+ : identifier ('[' ']')*
+ ;
+
+variableInitializer
+ : arrayInitializer
+ | expression
+ ;
+
+arrayInitializer
+ : '{' (variableInitializer (',' variableInitializer)* ','?)? '}'
+ ;
+
+classOrInterfaceType
+ : (identifier typeArguments? '.')* typeIdentifier typeArguments?
+ ;
+
+typeArgument
+ : typeType
+ | annotation* '?' ((EXTENDS | SUPER) typeType)?
+ ;
+
+qualifiedNameList
+ : qualifiedName (',' qualifiedName)*
+ ;
+
+formalParameters
+ : '(' (
+ receiverParameter?
+ | receiverParameter (',' formalParameterList)?
+ | formalParameterList?
+ ) ')'
+ ;
+
+receiverParameter
+ : typeType (identifier '.')* THIS
+ ;
+
+formalParameterList
+ : formalParameter (',' formalParameter)* (',' lastFormalParameter)?
+ | lastFormalParameter
+ ;
+
+formalParameter
+ : variableModifier* typeType variableDeclaratorId
+ ;
+
+lastFormalParameter
+ : variableModifier* typeType annotation* '...' variableDeclaratorId
+ ;
+
+// local variable type inference
+lambdaLVTIList
+ : lambdaLVTIParameter (',' lambdaLVTIParameter)*
+ ;
+
+lambdaLVTIParameter
+ : variableModifier* VAR identifier
+ ;
+
+qualifiedName
+ : identifier ('.' identifier)*
+ ;
+
+baseStringLiteral
+ : STRING_LITERAL
+ ;
+
+multilineStringLiteral
+ : MULTI_STRING_LIT
+ ;
+
+stringLiteral
+ : baseStringLiteral
+ | multilineStringLiteral
+ ;
+
+literal
+ : integerLiteral
+ | floatLiteral
+ | CHAR_LITERAL
+ | stringLiteral
+ | BOOL_LITERAL
+ | NULL_LITERAL
+ | TEXT_BLOCK // Java17
+ ;
+
+integerLiteral
+ : DECIMAL_LITERAL
+ | HEX_LITERAL
+ | OCT_LITERAL
+ | BINARY_LITERAL
+ ;
+
+floatLiteral
+ : FLOAT_LITERAL
+ | HEX_FLOAT_LITERAL
+ ;
+
+// ANNOTATIONS
+altAnnotationQualifiedName
+ : (identifier DOT)* '@' identifier
+ ;
+
+annotation
+ : ('@' qualifiedName | altAnnotationQualifiedName) (
+ '(' ( elementValuePairs | elementValue)? ')'
+ )?
+ ;
+
+elementValuePairs
+ : elementValuePair (',' elementValuePair)*
+ ;
+
+elementValuePair
+ : identifier '=' elementValue
+ ;
+
+elementValue
+ : expression
+ | annotation
+ | elementValueArrayInitializer
+ ;
+
+elementValueArrayInitializer
+ : '{' (elementValue (',' elementValue)*)? ','? '}'
+ ;
+
+annotationTypeDeclaration
+ : '@' INTERFACE identifier annotationTypeBody
+ ;
+
+annotationTypeBody
+ : '{' annotationTypeElementDeclaration* '}'
+ ;
+
+annotationTypeElementDeclaration
+ : modifier* annotationTypeElementRest
+ | ';' // this is not allowed by the grammar, but apparently allowed by the actual compiler
+ ;
+
+annotationTypeElementRest
+ : typeType annotationMethodOrConstantRest ';'
+ | classDeclaration ';'?
+ | interfaceDeclaration ';'?
+ | enumDeclaration ';'?
+ | annotationTypeDeclaration ';'?
+ | recordDeclaration ';'? // Java17
+ ;
+
+annotationMethodOrConstantRest
+ : annotationMethodRest
+ | annotationConstantRest
+ ;
+
+annotationMethodRest
+ : identifier '(' ')' defaultValue?
+ ;
+
+annotationConstantRest
+ : variableDeclarators
+ ;
+
+defaultValue
+ : DEFAULT elementValue
+ ;
+
+// MODULES - Java9
+
+moduleDeclaration
+ : OPEN? MODULE qualifiedName moduleBody
+ ;
+
+moduleBody
+ : '{' moduleDirective* '}'
+ ;
+
+moduleDirective
+ : REQUIRES requiresModifier* qualifiedName ';'
+ | EXPORTS qualifiedName (TO qualifiedName)? ';'
+ | OPENS qualifiedName (TO qualifiedName)? ';'
+ | USES qualifiedName ';'
+ | PROVIDES qualifiedName WITH qualifiedName ';'
+ ;
+
+requiresModifier
+ : TRANSITIVE
+ | STATIC
+ ;
+
+// RECORDS - Java 17
+
+recordDeclaration
+ : RECORD identifier typeParameters? recordHeader (IMPLEMENTS typeList)? recordBody
+ ;
+
+recordHeader
+ : '(' recordComponentList? ')'
+ ;
+
+recordComponentList
+ : recordComponent (',' recordComponent)*
+ ;
+
+recordComponent
+ : typeType identifier
+ ;
+
+recordBody
+ : '{' (classBodyDeclaration | compactConstructorDeclaration)* '}'
+ ;
+
+// STATEMENTS / BLOCKS
+
+block
+ : '{' blockStatement* '}'
+ ;
+
+blockStatement
+ : localVariableDeclaration ';'
+ | localTypeDeclaration
+ | statement
+ ;
+
+localVariableDeclaration
+ : variableModifier* (VAR identifier '=' expression | typeType variableDeclarators)
+ ;
+
+identifier
+ : IDENTIFIER
+ | MODULE
+ | OPEN
+ | REQUIRES
+ | EXPORTS
+ | OPENS
+ | TO
+ | USES
+ | PROVIDES
+ | WITH
+ | TRANSITIVE
+ | YIELD
+ | SEALED
+ | PERMITS
+ | RECORD
+ | VAR
+ ;
+
+typeIdentifier // Identifiers that are not restricted for type declarations
+ : IDENTIFIER
+ | MODULE
+ | OPEN
+ | REQUIRES
+ | EXPORTS
+ | OPENS
+ | TO
+ | USES
+ | PROVIDES
+ | WITH
+ | TRANSITIVE
+ | SEALED
+ | PERMITS
+ | RECORD
+ ;
+
+localTypeDeclaration
+ : classOrInterfaceModifier* (classDeclaration | interfaceDeclaration | recordDeclaration)
+ ;
+
+statement
+ : blockLabel = block
+ | ASSERT expression (':' expression)? ';'
+ | IF parExpression statement (ELSE statement)?
+ | FOR '(' forControl ')' statement
+ | WHILE parExpression statement
+ | DO statement WHILE parExpression ';'
+ | TRY block (catchClause+ finallyBlock? | finallyBlock)
+ | TRY resourceSpecification block catchClause* finallyBlock?
+ | SWITCH parExpression '{' switchBlockStatementGroup* switchLabel* '}'
+ | SYNCHRONIZED parExpression block
+ | RETURN expression? ';'
+ | THROW expression ';'
+ | BREAK identifier? ';'
+ | CONTINUE identifier? ';'
+ | YIELD expression ';' // Java17
+ | SEMI
+ | statementExpression = expression ';'
+ | switchExpression ';'? // Java17
+ | identifierLabel = identifier ':' statement
+ ;
+
+catchClause
+ : CATCH '(' variableModifier* catchType identifier ')' block
+ ;
+
+catchType
+ : qualifiedName ('|' qualifiedName)*
+ ;
+
+finallyBlock
+ : FINALLY block
+ ;
+
+resourceSpecification
+ : '(' resources ';'? ')'
+ ;
+
+resources
+ : resource (';' resource)*
+ ;
+
+resource
+ : variableModifier* (classOrInterfaceType variableDeclaratorId | VAR identifier) '=' expression
+ | qualifiedName
+ ;
+
+/** Matches cases then statements, both of which are mandatory.
+ * To handle empty cases at the end, we add switchLabel* to statement.
+ */
+switchBlockStatementGroup
+ : switchLabel+ blockStatement+
+ ;
+
+switchLabel
+ : CASE (
+ constantExpression = expression
+ | enumConstantName = IDENTIFIER
+ | typeType varName = identifier
+ ) ':'
+ | DEFAULT ':'
+ ;
+
+forControl
+ : enhancedForControl
+ | forInit? ';' expression? ';' forUpdate = expressionList?
+ ;
+
+forInit
+ : localVariableDeclaration
+ | expressionList
+ ;
+
+enhancedForControl
+ : variableModifier* (typeType | VAR) variableDeclaratorId ':' expression
+ ;
+
+// EXPRESSIONS
+
+parExpression
+ : '(' expression ')'
+ ;
+
+expressionList
+ : expression (',' expression)*
+ ;
+
+methodCall
+ : (identifier | THIS | SUPER) arguments
+ ;
+
+expression
+ // Expression order in accordance with https://introcs.cs.princeton.edu/java/11precedence/
+ // Level 16, Primary, array and member access
+ : primary #PrimaryExpression
+ | expression '[' expression ']' #SquareBracketExpression
+ | expression bop = '.' (
+ identifier
+ | methodCall
+ | THIS
+ | NEW nonWildcardTypeArguments? innerCreator
+ | SUPER superSuffix
+ | explicitGenericInvocation
+ ) #MemberReferenceExpression
+ // Method calls and method references are part of primary, and hence level 16 precedence
+ | methodCall #MethodCallExpression
+ | expression '::' typeArguments? identifier #MethodReferenceExpression
+ | typeType '::' (typeArguments? identifier | NEW) #MethodReferenceExpression
+ | classType '::' typeArguments? NEW #MethodReferenceExpression
+
+ // Java17
+ | switchExpression #ExpressionSwitch
+
+ // Level 15 Post-increment/decrement operators
+ | expression postfix = ('++' | '--') #PostIncrementDecrementOperatorExpression
+
+ // Level 14, Unary operators
+ | prefix = ('+' | '-' | '++' | '--' | '~' | '!') expression #UnaryOperatorExpression
+
+ // Level 13 Cast and object creation
+ | '(' annotation* typeType ('&' typeType)* ')' expression #CastExpression
+ | NEW creator #ObjectCreationExpression
+
+ // Level 12 to 1, Remaining operators
+ // Level 12, Multiplicative operators
+ | expression bop = ('*' | '/' | '%') expression #BinaryOperatorExpression
+ // Level 11, Additive operators
+ | expression bop = ('+' | '-') expression #BinaryOperatorExpression
+ // Level 10, Shift operators
+ | expression ('<' '<' | '>' '>' '>' | '>' '>') expression #BinaryOperatorExpression
+ // Level 9, Relational operators
+ | expression bop = ('<=' | '>=' | '>' | '<') expression #BinaryOperatorExpression
+ | expression bop = INSTANCEOF (typeType | pattern) #InstanceOfOperatorExpression
+ // Level 8, Equality Operators
+ | expression bop = ('==' | '!=') expression #BinaryOperatorExpression
+ // Level 7, Bitwise AND
+ | expression bop = '&' expression #BinaryOperatorExpression
+ // Level 6, Bitwise XOR
+ | expression bop = '^' expression #BinaryOperatorExpression
+ // Level 5, Bitwise OR
+ | expression bop = '|' expression #BinaryOperatorExpression
+ // Level 4, Logic AND
+ | expression bop = '&&' expression #BinaryOperatorExpression
+ // Level 3, Logic OR
+ | expression bop = '||' expression #BinaryOperatorExpression
+ // Level 2, Ternary
+ | expression bop = '?' expression ':' expression #TernaryExpression
+ // Level 1, Assignment
+ | expression bop = (
+ '='
+ | '+='
+ | '-='
+ | '*='
+ | '/='
+ | '&='
+ | '|='
+ | '^='
+ | '>>='
+ | '>>>='
+ | '<<='
+ | '%='
+ ) expression #BinaryOperatorExpression
+
+ // Level 0, Lambda Expression // Java8
+ | lambdaExpression #ExpressionLambda
+ ;
+
+// Java17
+pattern
+ : variableModifier* typeType annotation* identifier
+ ;
+
+// Java8
+lambdaExpression
+ : lambdaParameters '->' lambdaBody
+ ;
+
+// Java8
+lambdaParameters
+ : identifier
+ | '(' formalParameterList? ')'
+ | '(' identifier (',' identifier)* ')'
+ | '(' lambdaLVTIList? ')'
+ ;
+
+// Java8
+lambdaBody
+ : expression
+ | block
+ ;
+
+primary
+ : '(' expression ')'
+ | THIS
+ | SUPER
+ | literal
+ | identifier
+ | typeTypeOrVoid '.' CLASS
+ | nonWildcardTypeArguments (explicitGenericInvocationSuffix | THIS arguments)
+ ;
+
+// Java17
+switchExpression
+ : SWITCH parExpression '{' switchLabeledRule* '}'
+ ;
+
+// Java17
+switchLabeledRule
+ : CASE (expressionList | NULL_LITERAL | guardedPattern) (ARROW | COLON) switchRuleOutcome
+ | DEFAULT (ARROW | COLON) switchRuleOutcome
+ ;
+
+// Java17
+guardedPattern
+ : '(' guardedPattern ')'
+ | variableModifier* typeType annotation* identifier ('&&' expression)*
+ | guardedPattern '&&' expression
+ ;
+
+// Java17
+switchRuleOutcome
+ : block
+ | blockStatement*
+ ;
+
+classType
+ : (classOrInterfaceType '.')? annotation* identifier typeArguments?
+ ;
+
+creator
+ : nonWildcardTypeArguments? createdName classCreatorRest
+ | createdName arrayCreatorRest
+ ;
+
+createdName
+ : identifier typeArgumentsOrDiamond? ('.' identifier typeArgumentsOrDiamond?)*
+ | primitiveType
+ ;
+
+innerCreator
+ : identifier nonWildcardTypeArgumentsOrDiamond? classCreatorRest
+ ;
+
+arrayCreatorRest
+ : ('[' ']')+ arrayInitializer
+ | ('[' expression ']')+ ('[' ']')*
+ ;
+
+classCreatorRest
+ : arguments classBody?
+ ;
+
+explicitGenericInvocation
+ : nonWildcardTypeArguments explicitGenericInvocationSuffix
+ ;
+
+typeArgumentsOrDiamond
+ : '<' '>'
+ | typeArguments
+ ;
+
+nonWildcardTypeArgumentsOrDiamond
+ : '<' '>'
+ | nonWildcardTypeArguments
+ ;
+
+nonWildcardTypeArguments
+ : '<' typeList '>'
+ ;
+
+typeList
+ : typeType (',' typeType)*
+ ;
+
+typeType
+ : annotation* (classOrInterfaceType | primitiveType) (annotation* '[' ']')*
+ ;
+
+primitiveType
+ : BOOLEAN
+ | CHAR
+ | BYTE
+ | SHORT
+ | INT
+ | LONG
+ | FLOAT
+ | DOUBLE
+ ;
+
+typeArguments
+ : '<' typeArgument (',' typeArgument)* '>'
+ ;
+
+superSuffix
+ : arguments
+ | '.' typeArguments? identifier arguments?
+ ;
+
+explicitGenericInvocationSuffix
+ : SUPER superSuffix
+ | identifier arguments
+ ;
+
+arguments
+ : '(' expressionList? ')'
+ ;
\ No newline at end of file
diff --git a/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4 b/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4
new file mode 100644
index 000000000..2d4edc041
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4
@@ -0,0 +1,147 @@
+/**
+ * Based on Java 1.7 grammar for ANTLR 4, see Java.g4
+ *
+ * - changes main entry point to reflect sketch types 'static' | 'active'
+ * - adds support for type converter functions like "int()"
+ * - adds pseudo primitive type "color"
+ * - adds HTML hex notation with hash symbol: #ff5522
+ * - allow color to appear as part of qualified names (like in imports)
+ */
+
+grammar Processing;
+
+@lexer::members {
+ public static final int WHITESPACE = 1;
+ public static final int COMMENTS = 2;
+}
+
+@header {
+ package processing.mode.java.preproc;
+}
+
+// import Java grammar
+import JavaParser, JavaLexer;
+
+// main entry point, select sketch type
+processingSketch
+ : staticProcessingSketch
+ | javaProcessingSketch
+ | activeProcessingSketch
+// | warnMixedModes
+ ;
+
+// java mode, is a compilation unit
+javaProcessingSketch
+ : packageDeclaration? importDeclaration* typeDeclaration+ EOF
+ ;
+
+// No method declarations, just statements
+staticProcessingSketch
+ : (importDeclaration | blockStatement | typeDeclaration)* EOF
+ ;
+
+// active mode, has function definitions
+activeProcessingSketch
+ : (importDeclaration | classBodyDeclaration)* EOF
+ ;
+
+// User incorrectly mixing modes. Included to allow for kind error message.
+warnMixedModes
+ : (importDeclaration | classBodyDeclaration | blockStatement)* blockStatement classBodyDeclaration (importDeclaration | classBodyDeclaration | blockStatement)*
+ | (importDeclaration | classBodyDeclaration | blockStatement)* classBodyDeclaration blockStatement (importDeclaration | classBodyDeclaration | blockStatement)*
+ ;
+
+variableDeclaratorId
+ : warnTypeAsVariableName
+ | IDENTIFIER ('[' ']')*
+ ;
+
+// bug #93
+// https://github.com/processing/processing/issues/93
+// prevent from types being used as variable names
+warnTypeAsVariableName
+ : primitiveType ('[' ']')* {
+ notifyErrorListeners("Type names are not allowed as variable names: "+$primitiveType.text);
+ }
+ ;
+
+// catch special API function calls that we are interested in
+methodCall
+ : functionWithPrimitiveTypeName
+ | IDENTIFIER '(' expressionList? ')'
+ | THIS '(' expressionList? ')'
+ | SUPER '(' expressionList? ')'
+ ;
+
+// these are primitive type names plus "()"
+// "color" is a special Processing primitive (== int)
+functionWithPrimitiveTypeName
+ : ( 'boolean'
+ | 'byte'
+ | 'char'
+ | 'float'
+ | 'int'
+ | 'color'
+ ) '(' expressionList? ')'
+ ;
+
+// adding support for "color" primitive
+primitiveType
+ : BOOLEAN
+ | CHAR
+ | BYTE
+ | SHORT
+ | INT
+ | LONG
+ | FLOAT
+ | DOUBLE
+ | colorPrimitiveType
+ ;
+
+colorPrimitiveType
+ : 'color'
+ ;
+
+qualifiedName
+ : (IDENTIFIER | colorPrimitiveType) ('.' (IDENTIFIER | colorPrimitiveType))*
+ ;
+
+// added HexColorLiteral
+literal
+ : integerLiteral
+ | floatLiteral
+ | CHAR_LITERAL
+ | stringLiteral
+ | BOOL_LITERAL
+ | NULL_LITERAL
+ | hexColorLiteral
+ ;
+
+// As parser rule so this produces a separate listener
+// for us to alter its value.
+hexColorLiteral
+ : HexColorLiteral
+ ;
+
+// add color literal notations for
+// #ff5522
+HexColorLiteral
+ : '#' (HexDigit HexDigit)? HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit
+ ;
+
+// hide but do not remove whitespace and comments
+
+WS : [ \t\r\n\u000C]+ -> channel(1)
+ ;
+
+COMMENT
+ : '/*' .*? '*/' -> channel(2)
+ ;
+
+LINE_COMMENT
+ : '//' ~[\r\n]* -> channel(2)
+ ;
+
+CHAR_LITERAL
+ : '\'' (~['\\\r\n] | EscapeSequence)* '\'' // A bit nasty but let JDT tackle invalid chars
+ ;
\ No newline at end of file
diff --git a/java/preprocessor/src/main/java/processing/app/Platform.java b/java/preprocessor/src/main/java/processing/app/Platform.java
index 079d9d79c..b90974dd3 100644
--- a/java/preprocessor/src/main/java/processing/app/Platform.java
+++ b/java/preprocessor/src/main/java/processing/app/Platform.java
@@ -15,7 +15,21 @@ public class Platform {
}
settingsFolder = new File(appData + "\\Processing");
} else {
- settingsFolder = new File(System.getProperty("user.home") + "/.processing");
+ // Check to see if the user has set a different location for their config
+ String configHomeEnv = System.getenv("XDG_CONFIG_HOME");
+ if (configHomeEnv != null && !configHomeEnv.isBlank()) {
+ settingsFolder = new File(configHomeEnv);
+ if (!settingsFolder.exists()) {
+ settingsFolder = null; // don't use non-existent folder
+ }
+ }
+ String snapUserCommon = System.getenv("SNAP_USER_COMMON");
+ if (snapUserCommon != null && !snapUserCommon.isBlank()) {
+ settingsFolder = new File(snapUserCommon);
+ }
+ if (settingsFolder == null) {
+ settingsFolder = new File(System.getProperty("user.home"), ".config");
+ }
}
return settingsFolder;
}
diff --git a/java/preprocessor/src/main/java/processing/app/Preferences.java b/java/preprocessor/src/main/java/processing/app/Preferences.java
index 7ce476fde..eab3a2397 100644
--- a/java/preprocessor/src/main/java/processing/app/Preferences.java
+++ b/java/preprocessor/src/main/java/processing/app/Preferences.java
@@ -58,7 +58,7 @@ public class Preferences {
}
}
static public boolean getBoolean(String attribute) {
- String value = get(attribute); //, null);
+ String value = get(attribute);
return Boolean.parseBoolean(value);
}
static public int getInteger(String attribute /*, int defaultValue*/) {
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index 815792955..5a6e2d60b 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -29,7 +29,6 @@ import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -488,6 +487,10 @@ public class JavaEditor extends Editor {
* Handler for Sketch β Export Application
*/
public void handleExportApplication() {
+ if(service.getEnabled()){
+ service.export();
+ return;
+ }
if (handleExportCheckModified()) {
statusNotice(Language.text("export.notice.exporting"));
ExportPrompt ep = new ExportPrompt(this, () -> {
@@ -638,6 +641,12 @@ public class JavaEditor extends Editor {
protected void handleLaunch(boolean present, boolean tweak) {
prepareRun();
toolbar.activateRun();
+
+ if(this.service.getEnabled()){
+ this.service.run();
+ return;
+ }
+
synchronized (runtimeLock) {
runtimeLaunchRequested = true;
}
@@ -666,6 +675,16 @@ public class JavaEditor extends Editor {
* session or performs standard stop action if not currently debugging.
*/
public void handleStop() {
+ if(this.service.getEnabled()){
+ // TODO: Improve Gradle UI Feedback
+ toolbar.activateStop();
+ this.service.stop();
+
+ toolbar.deactivateStop();
+ toolbar.deactivateRun();
+ return;
+ }
+
if (debugger.isStarted()) {
debugger.stopDebug();
diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java
index cb4fd0001..c3edc810a 100644
--- a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java
+++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java
@@ -32,7 +32,6 @@ import org.antlr.v4.runtime.tree.ParseTree;
import processing.app.Base;
import processing.app.Preferences;
-import processing.core.PApplet;
import processing.mode.java.preproc.PdePreprocessor.Mode;
/**
@@ -1234,19 +1233,35 @@ public class PdeParseTreeListener extends ProcessingBaseListener {
{ // assemble line with applet args
StringJoiner argsJoiner = new StringJoiner(", ");
- boolean shouldFullScreen = Preferences.getBoolean("export.application.present");
- shouldFullScreen = shouldFullScreen || Preferences.getBoolean("export.application.fullscreen");
+ boolean shouldFullScreen;
+ String presentProp = System.getProperty("processing.fullscreen");
+
+ if (presentProp != null) {
+ shouldFullScreen = presentProp.equals("true");
+ } else {
+ boolean isExportedApp = Preferences.getBoolean("export.application.present");
+ boolean isFullscreenPref = Preferences.getBoolean("export.application.fullscreen");
+ shouldFullScreen = isExportedApp || isFullscreenPref;
+ }
+
if (shouldFullScreen) {
- argsJoiner.add("\"" + PApplet.ARGS_FULL_SCREEN + "\"");
+ argsJoiner.add("\"--full-screen\"");
- String bgColor = Preferences.get("run.present.bgcolor");
- argsJoiner.add("\"" + PApplet.ARGS_BGCOLOR + "=" + bgColor + "\"");
+ String bgColor = System.getProperty("processing.window.color", Preferences.get("run.present.bgcolor"));
+ argsJoiner.add("\"--bgcolor=" + bgColor + "\"");
- if (Preferences.getBoolean("export.application.stop")) {
+ boolean showStop;
+ var hideStop = System.getProperty("processing.stop.hide");
+ if(hideStop != null){
+ showStop = hideStop.equals("false");
+ }else{
+ showStop = Preferences.getBoolean("export.application.stop");
+ }
+ if(showStop) {
String stopColor = Preferences.get("run.present.stop.color");
- argsJoiner.add("\"" + PApplet.ARGS_STOP_COLOR + "=" + stopColor + "\"");
+ argsJoiner.add("\"--stop-color=" + stopColor + "\"");
} else {
- argsJoiner.add("\"" + PApplet.ARGS_HIDE_STOP + "\"");
+ argsJoiner.add("\"--hide-stop\"");
}
}
diff --git a/java/src/processing/mode/java/preproc/TextTransform.java b/java/src/processing/mode/java/preproc/TextTransform.java
index 77ae022f1..19ba8f3e5 100644
--- a/java/src/processing/mode/java/preproc/TextTransform.java
+++ b/java/src/processing/mode/java/preproc/TextTransform.java
@@ -8,8 +8,6 @@ import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
-import processing.core.PApplet;
-
public class TextTransform {
@@ -256,7 +254,7 @@ public class TextTransform {
i = -(i + 1);
i -= 1;
}
- i = PApplet.constrain(i, 0, outMap.size()-1);
+ i = constrain(i, 0, outMap.size()-1);
Edit edit = outMap.get(i);
int diff = outputOffset - edit.toOffset;
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
@@ -271,7 +269,7 @@ public class TextTransform {
i = -(i + 1);
i -= 1;
}
- i = PApplet.constrain(i, 0, inMap.size()-1);
+ i = constrain(i, 0, inMap.size()-1);
Edit edit = inMap.get(i);
int diff = inputOffset - edit.fromOffset;
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
@@ -283,6 +281,10 @@ public class TextTransform {
}
}
+ static public final int constrain(int amt, int low, int high) {
+ return (amt < low) ? low : ((amt > high) ? high : amt);
+ }
+
private static class CompositeOffsetMapper implements OffsetMapper {
private List mappers = new ArrayList<>();
diff --git a/java/test/resources/bug1532.pde b/java/test/resources/bug1532.pde
index 66b24b777..ae8ecdbf8 100644
--- a/java/test/resources/bug1532.pde
+++ b/java/test/resources/bug1532.pde
@@ -20,9 +20,9 @@ flatCube[][] grid;
void setup() {
try {
- quicktime.QTSession.open();
- }
- catch (quicktime.QTException qte) {
+ // quicktime.QTSession.open();
+ }
+ catch (quicktime.QTException qte) {
qte.printStackTrace();
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4bdcd880e..5809665d7 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,6 +5,7 @@ include(
"app",
"java",
"java:preprocessor",
+ "java:gradle",
"java:libraries:dxf",
"java:libraries:io",
"java:libraries:net",