From 971d98bba0a8acf24a9430015a7c1508e2c9d2a3 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 30 Jan 2025 22:59:16 +0100 Subject: [PATCH 01/12] 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 61703c19a5..b3203cbcdb 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 e859a2b123..accd19d2d2 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 { - 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 0000000000..b3de61fea0 --- /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 0000000000..1fa8ced282 --- /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 + | 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 0000000000..f16f5ba31e --- /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 f016cc474caa637f982c8dd21078801974aabb69 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 12:01:47 +0100 Subject: [PATCH 02/12] Removed core dependency from the pre-processor --- java/preprocessor/build.gradle.kts | 2 -- .../mode/java/preproc/PdeParseTreeListener.java | 9 ++++----- .../processing/mode/java/preproc/TextTransform.java | 10 ++++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts index accd19d2d2..713e1d5c09 100644 --- a/java/preprocessor/build.gradle.kts +++ b/java/preprocessor/build.gradle.kts @@ -23,8 +23,6 @@ sourceSets{ } dependencies{ - implementation(project(":core")) - implementation(libs.antlr) implementation(libs.eclipseJDT) diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java index cb4fd00010..2f9580f787 100644 --- a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java +++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java @@ -32,7 +32,6 @@ import processing.app.Base; import processing.app.Preferences; -import processing.core.PApplet; import processing.mode.java.preproc.PdePreprocessor.Mode; /** @@ -1237,16 +1236,16 @@ protected void writeMain(PrintWriterWithEditGen footerWriter, boolean shouldFullScreen = Preferences.getBoolean("export.application.present"); shouldFullScreen = shouldFullScreen || Preferences.getBoolean("export.application.fullscreen"); 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 + "\""); + argsJoiner.add("\"--bgcolor=" + bgColor + "\""); if (Preferences.getBoolean("export.application.stop")) { 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 77ae022f19..19ba8f3e53 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.ListIterator; import java.util.stream.Collectors; -import processing.core.PApplet; - public class TextTransform { @@ -256,7 +254,7 @@ public int getInputOffset(int outputOffset) { 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 int getOutputOffset(int inputOffset) { 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 OffsetMapper thenMapping(OffsetMapper mapper) { } } + 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<>(); From 23c8a4626ac370b560169df130d86d4b58d859e2 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 3 Feb 2025 12:07:01 +0100 Subject: [PATCH 03/12] - Imports cleanup - Disable system look & feel on macOS for readability - Cleaned unnecessary space - Cleaned finished TODO --- app/build.gradle.kts | 2 -- app/src/processing/app/Platform.java | 3 --- app/src/processing/app/UpdateCheck.java | 2 +- java/src/processing/mode/java/JavaBuild.java | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 773ce6514d..411fe7a92b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -59,8 +59,6 @@ compose.desktop { } entitlementsFile.set(project.file("entitlements.plist")) runtimeEntitlementsFile.set(project.file("entitlements.plist")) - // Allow swing to use the system look and feel - jvmArgs("-Dapple.awt.application.appearance=system") } windows{ iconFile = project.file("../build/windows/processing.ico") diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java index 23a8df0213..8d4d28ddcf 100644 --- a/app/src/processing/app/Platform.java +++ b/app/src/processing/app/Platform.java @@ -28,9 +28,6 @@ import java.lang.management.ManagementFactory; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.PosixFilePermission; import java.util.*; import com.sun.jna.platform.FileUtils; diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 1ec620a161..40ffe24c01 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -56,7 +56,7 @@ public class UpdateCheck { private final Base base; - static private final String DOWNLOAD_URL = System.getProperty("processing.download.page","https://processing.org/download/"); + static private final String DOWNLOAD_URL = System.getProperty("processing.download.page","https://processing.org/download/"); static private final String LATEST_URL = System.getProperty("processing.download.latest","https://processing.org/download/latest.txt"); static private final long ONE_DAY = 24 * 60 * 60 * 1000; diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java index 68a81eabb4..aed0bc1327 100644 --- a/java/src/processing/mode/java/JavaBuild.java +++ b/java/src/processing/mode/java/JavaBuild.java @@ -770,7 +770,6 @@ protected boolean exportApplication(File destFolder, } } - // TODO: Handle the java embed and Icon with the new build system } else if (exportPlatform == PConstants.LINUX) { if (embedJava) { From 5468da0fd6b42f790340acb3e2d00472ae5f42af Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 4 Feb 2025 16:18:29 +0100 Subject: [PATCH 04/12] Removed outdated ant backwards compatibility marks --- app/ant/processing/app/Schema.java | 3 +++ app/ant/processing/app/contrib/ui/ContributionManagerKt.java | 3 +++ app/build.gradle.kts | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/ant/processing/app/Schema.java b/app/ant/processing/app/Schema.java index 6e1e92ef23..3130c3dec7 100644 --- a/app/ant/processing/app/Schema.java +++ b/app/ant/processing/app/Schema.java @@ -2,6 +2,9 @@ import processing.app.ui.Editor; +// Stub class for backwards compatibility with the ant-build system +// This class is not used in the Gradle build system +// The actual implementation is in src/.../Schema.kt public class Schema { public static Editor handleSchema(String input, Base base) { return null; diff --git a/app/ant/processing/app/contrib/ui/ContributionManagerKt.java b/app/ant/processing/app/contrib/ui/ContributionManagerKt.java index 7b0ee30988..f7497bfbad 100644 --- a/app/ant/processing/app/contrib/ui/ContributionManagerKt.java +++ b/app/ant/processing/app/contrib/ui/ContributionManagerKt.java @@ -2,6 +2,9 @@ import processing.app.contrib.ContributionManager; +// Stub class for backwards compatibility with the ant-build system +// This class is not used in the Gradle build system +// The actual implementation is in src/.../ContributionManager.kt public final class ContributionManagerKt { public static void openContributionsManager() { ContributionManager.openLibraries(); diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 411fe7a92b..6a02906945 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,11 +32,9 @@ sourceSets{ main{ java{ srcDirs("src") - exclude("**/*Kt.java") } kotlin{ srcDirs("src") - exclude("**/*Kt.java") } } } From 1b8a8ed461cb39eb0489564589e144178fb1bef5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 09:10:42 +0100 Subject: [PATCH 05/12] Gradle Plugin from PoC --- .gitignore | 6 +- core/build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 +- java/gradle/build.gradle.kts | 34 +++++ .../src/main/kotlin/ProcessingPlugin.kt | 143 ++++++++++++++++++ java/gradle/src/main/kotlin/ProcessingTask.kt | 73 +++++++++ java/preprocessor/build.gradle.kts | 2 + settings.gradle.kts | 3 +- 8 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 java/gradle/build.gradle.kts create mode 100644 java/gradle/src/main/kotlin/ProcessingPlugin.kt create mode 100644 java/gradle/src/main/kotlin/ProcessingTask.kt diff --git a/.gitignore b/.gitignore index ebdb29b670..ab72f2264b 100644 --- a/.gitignore +++ b/.gitignore @@ -98,7 +98,9 @@ bin-test processing-examples # Maven ignores +/.kotlin/sessions .gradle +.build/ core/build/ build/publish/ app/build @@ -108,7 +110,5 @@ java/build/ /java/libraries/svg/bin /java/preprocessor/build /java/lsp/build -/.kotlin/sessions /core/examples/build - -.build/ +/java/gradle/build \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d4a1dcacbc..7f7438d771 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,7 +6,7 @@ plugins { alias(libs.plugins.mavenPublish) } -group = "org.processing" +version = rootProject.version repositories { mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3203cbcdb..c62f17a043 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,4 +31,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/gradle/build.gradle.kts b/java/gradle/build.gradle.kts new file mode 100644 index 0000000000..c08e316ef5 --- /dev/null +++ b/java/gradle/build.gradle.kts @@ -0,0 +1,34 @@ +plugins{ + `java-gradle-plugin` + alias(libs.plugins.gradlePublish) + + kotlin("jvm") version libs.versions.kotlin +} + +version = rootProject.version + +repositories { + mavenCentral() + maven { url = uri("https://jogamp.org/deployment/maven") } +} + +dependencies{ + implementation(project(":java:preprocessor")) + + implementation("org.jetbrains.compose:compose-gradle-plugin:1.7.3") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21") +} + +gradlePlugin{ + plugins{ + create("processing"){ + id = "org.processing" + implementationClass = "org.processing.gradle.ProcessingPlugin" + } + } +} +publishing{ + repositories{ + mavenLocal() + } +} \ 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 0000000000..e48c9b4d4a --- /dev/null +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -0,0 +1,143 @@ +package org.processing.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.jetbrains.compose.ComposeExtension +import org.jetbrains.compose.desktop.DesktopExtension +import java.io.File +import java.util.* +import javax.inject.Inject + +class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFactory) : Plugin { + override fun apply(project: Project) { + project.plugins.apply(JavaPlugin::class.java) + + // TODO: Only set the build directory when run from the Processing plugin + project.layout.buildDirectory.set(project.layout.projectDirectory.dir(".processing")) + + project.plugins.apply("org.jetbrains.compose") + project.plugins.apply("org.jetbrains.kotlin.jvm") + + project.dependencies.add("implementation", "org.processing:core:4.3.1") + project.dependencies.add("implementation", project.fileTree("src").apply { include("**/code/*.jar") }) + + // Base 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") + + project.repositories.add(project.repositories.maven { it.setUrl("https://jogamp.org/deployment/maven") }) + project.repositories.add(project.repositories.mavenCentral()) + + project.extensions.configure(ComposeExtension::class.java) { extension -> + extension.extensions.getByType(DesktopExtension::class.java).application { application -> + application.mainClass = project.layout.projectDirectory.asFile.name.replace(Regex("[^a-zA-Z0-9_]"), "_") + application.nativeDistributions.modules("java.management") + } + } + + // TODO: Also only do within Processing + project.tasks.named("wrapper").configure { + it.enabled = false + } + + project.tasks.create("sketch").apply { + group = "processing" + description = "Runs the Processing sketch" + dependsOn("run") + } + project.tasks.create("present").apply { + // TODO: Implement dynamic fullscreen by setting the properties and recompiling the sketch every run + group = "processing" + description = "Presents the Processing sketch" + dependsOn("run") + } + project.tasks.create("export").apply { + group = "processing" + description = "Creates a distributable version of the Processing sketch" + dependsOn("createDistributable") + } + + project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.all { sourceSet -> + // TODO: also supporting normal gradle setup + 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("./") + } + sourceSet.allSource.source(pdeSourceSet) + + val outputDirectory = project.layout.buildDirectory.file( "generated/pde/" + sourceSet.name).get().asFile + sourceSet.java.srcDir(outputDirectory) + + // TODO: Support multiple sketches? + // TODO: Preprocess PDE files in this step so we can add the library dependencies + + val taskName = sourceSet.getTaskName("preprocess", "PDE") + project.tasks.register(taskName, ProcessingTask::class.java) { task -> + task.description = "Processes the ${sourceSet.name} PDE" + task.source = pdeSourceSet + task.outputDirectory = outputDirectory + } + + project.tasks.named( + sourceSet.compileJavaTaskName + ) { task -> task.dependsOn(taskName) } + } + + var settingsFolder = File(System.getProperty("user.home"),".processing") + val osName = System.getProperty("os.name").lowercase() + if (osName.contains("win")) { + settingsFolder = File(System.getenv("APPDATA"), "Processing") + } else if (osName.contains("mac")) { + settingsFolder = File(System.getProperty("user.home"), "Library/Processing") + }else if (osName.contains("nix") || osName.contains("nux")) { + settingsFolder = File(System.getProperty("user.home"), ".processing") + } + + val preferences = File(settingsFolder, "preferences.txt") + val prefs = Properties() + prefs.load(preferences.inputStream()) + + val sketchbook = prefs.getProperty("sketchbook.path.four") + + File(sketchbook, "libraries").listFiles { file -> file.isDirectory + }?.forEach{ + project.dependencies.add("implementation", project.fileTree(it).apply { include("**/*.jar") }) + } + } + abstract class DefaultPDESourceDirectorySet @Inject constructor( + sourceDirectorySet: SourceDirectorySet, + taskDependencyFactory: TaskDependencyFactory + ) : DefaultSourceDirectorySet(sourceDirectorySet, taskDependencyFactory), SourceDirectorySet +} + diff --git a/java/gradle/src/main/kotlin/ProcessingTask.kt b/java/gradle/src/main/kotlin/ProcessingTask.kt new file mode 100644 index 0000000000..bca1b1bd34 --- /dev/null +++ b/java/gradle/src/main/kotlin/ProcessingTask.kt @@ -0,0 +1,73 @@ +package org.processing.gradle + +import org.gradle.api.file.* +import org.gradle.api.tasks.* +import org.gradle.internal.file.Deleter +import org.gradle.work.ChangeType +import org.gradle.work.FileChange +import org.gradle.work.InputChanges +import processing.mode.java.preproc.PdePreprocessor +import java.io.File +import java.io.IOException +import java.io.UncheckedIOException +import java.util.concurrent.Callable +import javax.inject.Inject + +abstract class ProcessingTask() : SourceTask() { + @get:OutputDirectory + var outputDirectory: File? = null + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:IgnoreEmptyDirectories + @get:SkipWhenEmpty + open val stableSources: FileCollection = project.files(Callable { this.source }) + + @TaskAction + fun execute(inputChanges: InputChanges) { + val files: MutableSet = HashSet() + if (inputChanges.isIncremental) { + var rebuildRequired = true + for (fileChange: FileChange in inputChanges.getFileChanges(stableSources)) { + if (fileChange.fileType == FileType.FILE) { + if (fileChange.changeType == ChangeType.REMOVED) { + rebuildRequired = true + break + } + files.add(fileChange.file) + } + } + if (rebuildRequired) { + try { + outputDirectory?.let { deleter.ensureEmptyDirectory(it) } + } catch (ex: IOException) { + throw UncheckedIOException(ex) + } + files.addAll(stableSources.files) + } + } else { + files.addAll(stableSources.files) + } + + val name = project.layout.projectDirectory.asFile.name.replace(Regex("[^a-zA-Z0-9_]"), "_") + val combined = files.joinToString("\n") { it.readText() } + File(outputDirectory, "$name.java") + .bufferedWriter() + .use { out -> + val meta = PdePreprocessor + .builderFor(name) + .build() + .write(out, combined) + + + // TODO: Only import the libraries that are actually used + val importStatement = meta.importStatements + } + } + + @get:Inject + open val deleter: Deleter + get() { + throw UnsupportedOperationException("Decorator takes care of injection") + } +} \ No newline at end of file diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts index 713e1d5c09..d4f34d99cf 100644 --- a/java/preprocessor/build.gradle.kts +++ b/java/preprocessor/build.gradle.kts @@ -6,6 +6,8 @@ plugins{ alias(libs.plugins.mavenPublish) } + + repositories{ mavenCentral() google() diff --git a/settings.gradle.kts b/settings.gradle.kts index 4bdcd880e8..c4e6a2d38b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,10 +1,11 @@ -rootProject.name = "processing" +rootProject.name = "org.processing" include( "core", "core:examples", "app", "java", "java:preprocessor", + "java:gradle", "java:libraries:dxf", "java:libraries:io", "java:libraries:net", From f4d7fbf99b399889de6b6d0bbafa0148dcb84782 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 09:19:08 +0100 Subject: [PATCH 06/12] Changed naming --- java/gradle/build.gradle.kts | 4 ++-- java/gradle/src/main/kotlin/ProcessingPlugin.kt | 2 +- java/gradle/src/main/kotlin/ProcessingTask.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts index c08e316ef5..25bc97f888 100644 --- a/java/gradle/build.gradle.kts +++ b/java/gradle/build.gradle.kts @@ -22,8 +22,8 @@ dependencies{ gradlePlugin{ plugins{ create("processing"){ - id = "org.processing" - implementationClass = "org.processing.gradle.ProcessingPlugin" + id = "org.processing.java.gradle" + implementationClass = "org.processing.java.gradle.ProcessingPlugin" } } } diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt index e48c9b4d4a..9701e29ff6 100644 --- a/java/gradle/src/main/kotlin/ProcessingPlugin.kt +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -1,4 +1,4 @@ -package org.processing.gradle +package org.processing.java.gradle import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/java/gradle/src/main/kotlin/ProcessingTask.kt b/java/gradle/src/main/kotlin/ProcessingTask.kt index bca1b1bd34..66e493aa61 100644 --- a/java/gradle/src/main/kotlin/ProcessingTask.kt +++ b/java/gradle/src/main/kotlin/ProcessingTask.kt @@ -1,4 +1,4 @@ -package org.processing.gradle +package org.processing.java.gradle import org.gradle.api.file.* import org.gradle.api.tasks.* From 7f00d5b0289dda41d92b126149a81f20d1ccdc31 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 12:36:18 +0100 Subject: [PATCH 07/12] added example of plugin usage --- .gitignore | 3 +- gradle/libs.versions.toml | 2 ++ java/gradle/build.gradle.kts | 5 ++- java/gradle/example/brightness.pde | 32 +++++++++++++++++++ java/gradle/example/build.gradle.kts | 3 ++ java/gradle/example/settings.gradle.kts | 6 ++++ .../src/main/kotlin/ProcessingPlugin.kt | 3 +- java/preprocessor/build.gradle.kts | 9 ++++-- 8 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 java/gradle/example/brightness.pde create mode 100644 java/gradle/example/build.gradle.kts create mode 100644 java/gradle/example/settings.gradle.kts diff --git a/.gitignore b/.gitignore index ab72f2264b..68cdd251d2 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,5 @@ java/build/ /java/preprocessor/build /java/lsp/build /core/examples/build -/java/gradle/build \ No newline at end of file +/java/gradle/build +/java/gradle/example/.processing diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c62f17a043..9ac004b4c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,8 @@ 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" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts index 25bc97f888..e8c08eb3c2 100644 --- a/java/gradle/build.gradle.kts +++ b/java/gradle/build.gradle.kts @@ -9,14 +9,13 @@ version = rootProject.version repositories { mavenCentral() - maven { url = uri("https://jogamp.org/deployment/maven") } } dependencies{ implementation(project(":java:preprocessor")) - implementation("org.jetbrains.compose:compose-gradle-plugin:1.7.3") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21") + implementation(libs.composeGradlePlugin) + implementation(libs.kotlinGradlePlugin) } gradlePlugin{ diff --git a/java/gradle/example/brightness.pde b/java/gradle/example/brightness.pde new file mode 100644 index 0000000000..46ea60820a --- /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); + 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; + } +} \ No newline at end of file diff --git a/java/gradle/example/build.gradle.kts b/java/gradle/example/build.gradle.kts new file mode 100644 index 0000000000..1dd4907f36 --- /dev/null +++ b/java/gradle/example/build.gradle.kts @@ -0,0 +1,3 @@ +plugins{ + id("org.processing.java.gradle") version "4.4.0" +} \ 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 0000000000..588dcaf850 --- /dev/null +++ b/java/gradle/example/settings.gradle.kts @@ -0,0 +1,6 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt index 9701e29ff6..f775425e25 100644 --- a/java/gradle/src/main/kotlin/ProcessingPlugin.kt +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -130,8 +130,7 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact val sketchbook = prefs.getProperty("sketchbook.path.four") - File(sketchbook, "libraries").listFiles { file -> file.isDirectory - }?.forEach{ + File(sketchbook, "libraries").listFiles { file -> file.isDirectory }?.forEach{ project.dependencies.add("implementation", project.fileTree(it).apply { include("**/*.jar") }) } } diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts index d4f34d99cf..a7bad0cced 100644 --- a/java/preprocessor/build.gradle.kts +++ b/java/preprocessor/build.gradle.kts @@ -6,7 +6,7 @@ plugins{ alias(libs.plugins.mavenPublish) } - +version = rootProject.version repositories{ mavenCentral() @@ -21,7 +21,12 @@ sourceSets{ include("processing/mode/java/preproc/**/*", "processing/app/**/*") } } - +} +afterEvaluate{ + tasks.withType(Jar::class.java){ + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(tasks.generateGrammarSource) + } } dependencies{ From 44ab816ca9fe2113f1868c3bda3372afaa9fec5d Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 20:38:27 +0100 Subject: [PATCH 08/12] disable full screen from settings and fixed a few bugs --- gradle/libs.versions.toml | 1 + java/gradle/build.gradle.kts | 1 + .../src/main/kotlin/ProcessingPlugin.kt | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ac004b4c9..d4ec11baf0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ 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" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts index e8c08eb3c2..9193c45d4e 100644 --- a/java/gradle/build.gradle.kts +++ b/java/gradle/build.gradle.kts @@ -16,6 +16,7 @@ dependencies{ implementation(libs.composeGradlePlugin) implementation(libs.kotlinGradlePlugin) + implementation(libs.kotlinComposePlugin) } gradlePlugin{ diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt index f775425e25..c2d108e3c0 100644 --- a/java/gradle/src/main/kotlin/ProcessingPlugin.kt +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -9,7 +9,9 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPluginExtension import org.jetbrains.compose.ComposeExtension +import org.jetbrains.compose.ComposePlugin import org.jetbrains.compose.desktop.DesktopExtension +import org.jetbrains.kotlin.konan.properties.saveToFile import java.io.File import java.util.* import javax.inject.Inject @@ -23,8 +25,9 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact project.plugins.apply("org.jetbrains.compose") project.plugins.apply("org.jetbrains.kotlin.jvm") + project.plugins.apply("org.jetbrains.kotlin.plugin.compose") - project.dependencies.add("implementation", "org.processing:core:4.3.1") + project.dependencies.add("implementation", "org.processing:core:4.4.0") project.dependencies.add("implementation", project.fileTree("src").apply { include("**/code/*.jar") }) // Base JOGL and Gluegen dependencies @@ -53,6 +56,7 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact 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()) project.extensions.configure(ComposeExtension::class.java) { extension -> extension.extensions.getByType(DesktopExtension::class.java).application { application -> @@ -72,7 +76,7 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact dependsOn("run") } project.tasks.create("present").apply { - // TODO: Implement dynamic fullscreen by setting the properties and recompiling the sketch every run + // TODO: Implement dynamic fullscreen by adding an argument to the task. This will require a change to core group = "processing" description = "Presents the Processing sketch" dependsOn("run") @@ -80,7 +84,14 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact project.tasks.create("export").apply { group = "processing" description = "Creates a distributable version of the Processing sketch" + dependsOn("createDistributable") + doLast{ + project.copy { + it.from(project.tasks.named("createDistributable").get().outputs.files) + it.into(project.layout.projectDirectory) + } + } } project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.all { sourceSet -> @@ -127,6 +138,10 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact val preferences = File(settingsFolder, "preferences.txt") val prefs = Properties() prefs.load(preferences.inputStream()) + prefs.setProperty("export.application.fullscreen", "false") + prefs.setProperty("export.application.present", "false") + prefs.setProperty("export.application.stop", "false") + prefs.store(preferences.outputStream(), null) val sketchbook = prefs.getProperty("sketchbook.path.four") From da852b45a109e27b3b3226fd78a0efd41b695fad Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 5 Feb 2025 20:54:32 +0100 Subject: [PATCH 09/12] Fullscreen support --- core/src/processing/core/PApplet.java | 4 ++-- java/gradle/src/main/kotlin/ProcessingPlugin.kt | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index 24c0d5d39a..78516dff8a 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -9938,8 +9938,8 @@ static public void runSketch(final String[] args, boolean hideStop = false; int displayNum = -1; // use default - boolean present = false; - boolean fullScreen = false; + boolean present = System.getProperty("processing.present", "false").equals("true"); + boolean fullScreen = System.getProperty("processing.fullscreen", "false").equals("true"); float uiScale = 0; String param, value; diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt index c2d108e3c0..1d902b9205 100644 --- a/java/gradle/src/main/kotlin/ProcessingPlugin.kt +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -8,6 +8,7 @@ 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.ComposePlugin import org.jetbrains.compose.desktop.DesktopExtension @@ -76,10 +77,14 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact dependsOn("run") } project.tasks.create("present").apply { - // TODO: Implement dynamic fullscreen by adding an argument to the task. This will require a change to core group = "processing" description = "Presents the Processing sketch" - dependsOn("run") + doFirst{ + project.tasks.withType(JavaExec::class.java).configureEach{ task -> + task.systemProperty("processing.fullscreen", "true") + } + } + finalizedBy("run") } project.tasks.create("export").apply { group = "processing" From ad5c27ec42dc6f479c1c4cee2b119568ba8fa252 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 17 Mar 2025 15:11:24 +0100 Subject: [PATCH 10/12] Apply same parser & lexer fixes from before --- .gitignore | 1 + java/preprocessor/src/main/antlr/JavaLexer.g4 | 2 ++ java/preprocessor/src/main/antlr/JavaParser.g4 | 15 ++++++++++++++- .../processing/mode/java/preproc/Processing.g4 | 15 +++------------ .../src/main/java/processing/app/Preferences.java | 2 +- java/test/resources/bug1532.pde | 6 +++--- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 7faa26d70f..0e321ec1ab 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,7 @@ java/build/ /java/libraries/svg/bin /java/preprocessor/build /java/lsp/build +/java/gradle/build /.kotlin/sessions /core/examples/build diff --git a/java/preprocessor/src/main/antlr/JavaLexer.g4 b/java/preprocessor/src/main/antlr/JavaLexer.g4 index b3de61fea0..b924864ea2 100644 --- a/java/preprocessor/src/main/antlr/JavaLexer.g4 +++ b/java/preprocessor/src/main/antlr/JavaLexer.g4 @@ -134,6 +134,8 @@ CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence) '\''; STRING_LITERAL: '"' (~["\\\r\n] | EscapeSequence)* '"'; +MULTI_STRING_LIT: '"""' (~[\\] | EscapeSequence)*? '"""'; + TEXT_BLOCK: '"""' [ \t]* [\r\n] (. | EscapeSequence)*? '"""'; NULL_LITERAL: 'null'; diff --git a/java/preprocessor/src/main/antlr/JavaParser.g4 b/java/preprocessor/src/main/antlr/JavaParser.g4 index 1fa8ced282..d273fa8885 100644 --- a/java/preprocessor/src/main/antlr/JavaParser.g4 +++ b/java/preprocessor/src/main/antlr/JavaParser.g4 @@ -314,11 +314,24 @@ qualifiedName : identifier ('.' identifier)* ; +baseStringLiteral + : STRING_LITERAL + ; + +multilineStringLiteral + : MULTI_STRING_LIT + ; + +stringLiteral + : baseStringLiteral + | multilineStringLiteral + ; + literal : integerLiteral | floatLiteral | CHAR_LITERAL - | STRING_LITERAL + | stringLiteral | BOOL_LITERAL | NULL_LITERAL | TEXT_BLOCK // Java17 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 index f16f5ba31e..2d4edc041a 100644 --- a/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4 +++ b/java/preprocessor/src/main/antlr/processing/mode/java/preproc/Processing.g4 @@ -27,7 +27,7 @@ processingSketch : staticProcessingSketch | javaProcessingSketch | activeProcessingSketch - | warnMixedModes +// | warnMixedModes ; // java mode, is a compilation unit @@ -111,7 +111,7 @@ literal : integerLiteral | floatLiteral | CHAR_LITERAL - | STRING_LITERAL + | stringLiteral | BOOL_LITERAL | NULL_LITERAL | hexColorLiteral @@ -144,13 +144,4 @@ LINE_COMMENT 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 + ; \ No newline at end of file diff --git a/java/preprocessor/src/main/java/processing/app/Preferences.java b/java/preprocessor/src/main/java/processing/app/Preferences.java index 7ce476fdea..eab3a23974 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 @@ static public String get(String attribute /*, String defaultValue */) { } } 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/test/resources/bug1532.pde b/java/test/resources/bug1532.pde index 66b24b7779..ae8ecdbf8b 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(); } From e01d02a8af6aa7ddc53e5a1900561473a2ed6a99 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Mon, 17 Mar 2025 15:31:25 +0100 Subject: [PATCH 11/12] Update settings.gradle.kts --- settings.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index c4e6a2d38b..de948b4688 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "org.processing" +rootProject.name = "processing" include( "core", "core:examples", @@ -12,4 +12,4 @@ include( "java:libraries:pdf", "java:libraries:serial", "java:libraries:svg", -) \ No newline at end of file +) From ba496569d8f048448dd6e8e92b2faaf443cf27a7 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 18 Mar 2025 10:15:40 +0100 Subject: [PATCH 12/12] Adding tests and direct linking --- .idea/jarRepositories.xml | 5 ++ java/gradle/build.gradle.kts | 6 +-- java/gradle/example/build.gradle.kts | 2 +- java/gradle/example/settings.gradle.kts | 7 ++- .../src/main/kotlin/ProcessingPlugin.kt | 11 ++--- java/gradle/src/main/kotlin/ProcessingTask.kt | 6 ++- .../src/test/kotlin/ProcessingPluginTest.kt | 48 +++++++++++++++++++ settings.gradle.kts | 2 +- 8 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 java/gradle/src/test/kotlin/ProcessingPluginTest.kt diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index e9be690395..0608b9af03 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -26,5 +26,10 @@ \ No newline at end of file diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts index 9193c45d4e..dcfc8bac45 100644 --- a/java/gradle/build.gradle.kts +++ b/java/gradle/build.gradle.kts @@ -5,8 +5,6 @@ plugins{ kotlin("jvm") version libs.versions.kotlin } -version = rootProject.version - repositories { mavenCentral() } @@ -17,12 +15,14 @@ dependencies{ implementation(libs.composeGradlePlugin) implementation(libs.kotlinGradlePlugin) implementation(libs.kotlinComposePlugin) + + testImplementation(libs.junit) } gradlePlugin{ plugins{ create("processing"){ - id = "org.processing.java.gradle" + id = "processing.java.gradle" implementationClass = "org.processing.java.gradle.ProcessingPlugin" } } diff --git a/java/gradle/example/build.gradle.kts b/java/gradle/example/build.gradle.kts index 1dd4907f36..d0cb1919e9 100644 --- a/java/gradle/example/build.gradle.kts +++ b/java/gradle/example/build.gradle.kts @@ -1,3 +1,3 @@ plugins{ - id("org.processing.java.gradle") version "4.4.0" + id("processing.java.gradle") } \ No newline at end of file diff --git a/java/gradle/example/settings.gradle.kts b/java/gradle/example/settings.gradle.kts index 588dcaf850..ee9c97e155 100644 --- a/java/gradle/example/settings.gradle.kts +++ b/java/gradle/example/settings.gradle.kts @@ -1,6 +1,5 @@ +rootProject.name = "processing-gradle-plugin-demo" + pluginManagement { - repositories { - mavenLocal() - gradlePluginPortal() - } + includeBuild("../../../") } \ No newline at end of file diff --git a/java/gradle/src/main/kotlin/ProcessingPlugin.kt b/java/gradle/src/main/kotlin/ProcessingPlugin.kt index 1d902b9205..158af74f4b 100644 --- a/java/gradle/src/main/kotlin/ProcessingPlugin.kt +++ b/java/gradle/src/main/kotlin/ProcessingPlugin.kt @@ -10,9 +10,7 @@ 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.ComposePlugin import org.jetbrains.compose.desktop.DesktopExtension -import org.jetbrains.kotlin.konan.properties.saveToFile import java.io.File import java.util.* import javax.inject.Inject @@ -28,10 +26,13 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact project.plugins.apply("org.jetbrains.kotlin.jvm") project.plugins.apply("org.jetbrains.kotlin.plugin.compose") + // TODO: Add to tests project.dependencies.add("implementation", "org.processing:core:4.4.0") + // TODO: Add tests project.dependencies.add("implementation", project.fileTree("src").apply { include("**/code/*.jar") }) // Base JOGL and Gluegen dependencies + // TODO: Add only if user is compiling for P2D or P3D 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") @@ -65,11 +66,8 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact application.nativeDistributions.modules("java.management") } } - // TODO: Also only do within Processing - project.tasks.named("wrapper").configure { - it.enabled = false - } + project.tasks.findByName("wrapper")?.enabled = false project.tasks.create("sketch").apply { group = "processing" @@ -116,7 +114,6 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact sourceSet.java.srcDir(outputDirectory) // TODO: Support multiple sketches? - // TODO: Preprocess PDE files in this step so we can add the library dependencies val taskName = sourceSet.getTaskName("preprocess", "PDE") project.tasks.register(taskName, ProcessingTask::class.java) { task -> diff --git a/java/gradle/src/main/kotlin/ProcessingTask.kt b/java/gradle/src/main/kotlin/ProcessingTask.kt index 66e493aa61..88041ee1c4 100644 --- a/java/gradle/src/main/kotlin/ProcessingTask.kt +++ b/java/gradle/src/main/kotlin/ProcessingTask.kt @@ -13,7 +13,7 @@ import java.io.UncheckedIOException import java.util.concurrent.Callable import javax.inject.Inject -abstract class ProcessingTask() : SourceTask() { +abstract class ProcessingTask : SourceTask() { @get:OutputDirectory var outputDirectory: File? = null @@ -62,6 +62,10 @@ abstract class ProcessingTask() : SourceTask() { // TODO: Only import the libraries that are actually used val importStatement = meta.importStatements + +// for (import in importStatement) { +// project.dependencies.add("implementation", import) +// } } } diff --git a/java/gradle/src/test/kotlin/ProcessingPluginTest.kt b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt new file mode 100644 index 0000000000..a98bc16d95 --- /dev/null +++ b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt @@ -0,0 +1,48 @@ +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{ + @Test + fun testPluginAddsSketchTask(){ + val project = ProjectBuilder.builder().build() + project.pluginManager.apply("processing.java.gradle") + + assert(project.tasks.getByName("sketch") is Task) + } + @JvmField + @Rule + val folder: TemporaryFolder = TemporaryFolder() + + @Test + fun testPluginOutcome() { + + val buildFile = folder.newFile("build.gradle.kts") + buildFile.writeText(""" + plugins{ + id("processing.java.gradle") + } + """.trimIndent()) + + val sketchFile = folder.newFile("sketch.pde") + sketchFile.writeText(""" + void setup(){ + size(100, 100); + } + void draw(){ + background(0); + } + """.trimIndent()) + + val result = GradleRunner.create() + .withProjectDir(folder.root) + .withArguments("build") + .withPluginClasspath() + .build() + + assert(folder.root.resolve(".processing/generated/pde/main").exists()) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index de948b4688..5809665d7c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,4 +12,4 @@ include( "java:libraries:pdf", "java:libraries:serial", "java:libraries:svg", -) +) \ No newline at end of file