From 971d98bba0a8acf24a9430015a7c1508e2c9d2a3 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Thu, 30 Jan 2025 22:59:16 +0100
Subject: [PATCH 1/2] Preprocessor with antlr Plugin

---
 gradle/libs.versions.toml                     |   3 +
 java/preprocessor/build.gradle.kts            |  21 +-
 java/preprocessor/src/main/antlr/JavaLexer.g4 | 233 +++++
 .../preprocessor/src/main/antlr/JavaParser.g4 | 813 ++++++++++++++++++
 .../mode/java/preproc/Processing.g4           | 156 ++++
 5 files changed, 1212 insertions(+), 14 deletions(-)
 create mode 100644 java/preprocessor/src/main/antlr/JavaLexer.g4
 create mode 100644 java/preprocessor/src/main/antlr/JavaParser.g4
 create mode 100644 java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 61703c19a..b3203cbcd 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"
 
 [libraries]
 jogl = { module = "org.jogamp.jogl:jogl-all-main", version.ref = "jogl" }
@@ -21,6 +22,8 @@ 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" }
 
 [plugins]
 jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts
index e859a2b12..accd19d2d 100644
--- a/java/preprocessor/build.gradle.kts
+++ b/java/preprocessor/build.gradle.kts
@@ -1,12 +1,11 @@
 import com.vanniktech.maven.publish.SonatypeHost
 
 plugins{
-    id("java")
+    java
+    antlr
     alias(libs.plugins.mavenPublish)
 }
 
-group = "org.processing"
-
 repositories{
     mavenCentral()
     google()
@@ -16,7 +15,7 @@ repositories{
 sourceSets{
     main{
         java{
-            srcDirs("src/main/java", "../src/", "../generated/")
+            srcDirs("src/main/java", "../src/")
             include("processing/mode/java/preproc/**/*", "processing/app/**/*")
         }
     }
@@ -24,10 +23,13 @@ sourceSets{
 }
 
 dependencies{
+    implementation(project(":core"))
+
     implementation(libs.antlr)
     implementation(libs.eclipseJDT)
 
-    implementation(project(":core"))
+    antlr(libs.antlr4)
+    implementation(libs.antlr4Runtime)
 }
 
 mavenPublishing{
@@ -60,13 +62,4 @@ mavenPublishing{
             developerConnection.set("scm:git:ssh://git@github.com/processing/processing4.git")
         }
     }
-}
-tasks.withType<Jar> {
-    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..b3de61fea
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/JavaLexer.g4
@@ -0,0 +1,233 @@
+/*
+ [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)* '"';
+
+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..1fa8ced28
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/JavaParser.g4
@@ -0,0 +1,813 @@
+/*
+ [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)*
+    ;
+
+literal
+    : integerLiteral
+    | floatLiteral
+    | CHAR_LITERAL
+    | STRING_LITERAL
+    | 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
+    | <assoc = right> expression bop = '?' expression ':' expression #TernaryExpression
+    // Level 1, Assignment
+    | <assoc = right> 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..f16f5ba31
--- /dev/null
+++ b/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4
@@ -0,0 +1,156 @@
+/**
+ *  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
+    | STRING_LITERAL
+    | 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
+		;
+
+// Parser Rules
+multilineStringLiteral
+    : MULTILINE_STRING_START .*? MULTILINE_STRING_END
+    ;
+
+// Lexer Rules
+MULTILINE_STRING_START: '"""' '\r'? '\n';
+MULTILINE_STRING_END: '"""';
\ No newline at end of file

From bf6b466179b3291ac3481b6f80f8ffe3a57b3151 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Fri, 31 Jan 2025 13:23:05 +0100
Subject: [PATCH 2/2] proper gradle configuration

---
 java/lsp/build.gradle.kts | 27 +++++++++++++++++++--------
 settings.gradle.kts       |  1 +
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/java/lsp/build.gradle.kts b/java/lsp/build.gradle.kts
index 6818093a2..b6e7034a1 100644
--- a/java/lsp/build.gradle.kts
+++ b/java/lsp/build.gradle.kts
@@ -1,11 +1,16 @@
 import com.vanniktech.maven.publish.SonatypeHost
 
 plugins{
-    id("java")
-    id("com.vanniktech.maven.publish") version "0.30.0"
+    java
+    application
+
+    alias(libs.plugins.mavenPublish)
 }
 
-group = "org.processing"
+application{
+    mainClass = "processing.mode.java.lsp.PdeLanguageServer"
+    applicationDefaultJvmArgs = listOf("-Djna.nosys=true","-Djava.awt.headless=true")
+}
 
 repositories{
     mavenCentral()
@@ -24,13 +29,13 @@ sourceSets{
 
 dependencies{
     implementation(project(":core"))
+    implementation(project(":app"))
+    implementation(project(":java"))
     implementation(project(":java:preprocessor"))
 
-    implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
-    implementation("org.jsoup:jsoup:1.17.2")
-    implementation("org.eclipse.jdt:org.eclipse.jdt.core:3.40.0")
-
-    implementation("org.processing:core:${version}")
+    implementation(libs.lsp4j)
+    implementation(libs.jsoup)
+    implementation(libs.eclipseJDT)
 }
 
 mavenPublishing{
@@ -63,4 +68,10 @@ mavenPublishing{
             developerConnection.set("scm:git:ssh://git@github.com/processing/processing4.git")
         }
     }
+}
+tasks.installDist {
+    duplicatesStrategy = DuplicatesStrategy.INCLUDE
+}
+tasks.withType<Copy> {
+    duplicatesStrategy = DuplicatesStrategy.INCLUDE
 }
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4bdcd880e..b459924d9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,6 +5,7 @@ include(
     "app",
     "java",
     "java:preprocessor",
+    "java:lsp",
     "java:libraries:dxf",
     "java:libraries:io",
     "java:libraries:net",