From 5faf2a10a6fc12e1280144baa6d868fd36ed30c9 Mon Sep 17 00:00:00 2001 From: A Pottinger Date: Thu, 10 Oct 2019 08:36:34 -0700 Subject: [PATCH] Refactor within PdePreprocessor to allow for override of edits. In response to https://github.com/processing/processing4/issues/11, allow client code to override preprocessing edit behabior by providing a subclass of PdeParseTreeListener. This does make the construction for PdePreprocessor.java a little bit messier but a builder may help and moving dependent types within enclosing classes can hopefully keep things coherent. --- java/src/processing/mode/java/JavaBuild.java | 3 +- java/src/processing/mode/java/JavaEditor.java | 2 +- .../preproc/PdeParseTreeErrorListener.java | 39 ---- .../java/preproc/PdeParseTreeListener.java | 14 ++ .../mode/java/preproc/PdePreprocessor.java | 211 +++++++++++++++--- .../mode/java/preproc/SourceEmitter.java | 37 --- .../java/preproc/issue/PdeIssueEmitter.java | 29 ++- .../issue/PdePreprocessIssueListener.java | 9 - .../mode/java/ProcessingTestUtil.java | 8 +- 9 files changed, 232 insertions(+), 120 deletions(-) delete mode 100644 java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java delete mode 100644 java/src/processing/mode/java/preproc/SourceEmitter.java delete mode 100644 java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java index 5c3e79016..9df5a6b94 100644 --- a/java/src/processing/mode/java/JavaBuild.java +++ b/java/src/processing/mode/java/JavaBuild.java @@ -139,7 +139,8 @@ public class JavaBuild { * @return null if compilation failed, main class name if not */ public String preprocess(File srcFolder, boolean sizeWarning) throws SketchException { - return preprocess(srcFolder, null, new PdePreprocessor(sketch.getName()), sizeWarning); + PdePreprocessor preprocessor = PdePreprocessor.builderFor(sketch.getName()).build(); + return preprocess(srcFolder, null, preprocessor, sizeWarning); } diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 61fd15144..e181601f3 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -125,7 +125,7 @@ public class JavaEditor extends Editor { public PdePreprocessor createPreprocessor(final String sketchName) { - return new PdePreprocessor(sketchName); + return PdePreprocessor.builderFor(sketchName).build(); } diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java deleted file mode 100644 index 2662ada7b..000000000 --- a/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* -Part of the Processing project - http://processing.org - -Copyright (c) 2012-19 The Processing Foundation - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License version 2 -as published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software Foundation, -Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package processing.mode.java.preproc; - -import processing.mode.java.preproc.issue.PdePreprocessIssue; - - -/** - * Listener for issues encountered while processing a valid pde parse tree. - */ -interface PdeParseTreeErrorListener { - - /** - * Callback to invoke when an issue is encountered while processing a valid PDE parse tree. - * - * @param issue The issue reported. - */ - void onError(PdePreprocessIssue issue); - -} diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java index c156e73b0..5c859ac88 100644 --- a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java +++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java @@ -705,4 +705,18 @@ public class PdeParseTreeListener extends ProcessingBaseListener { return builder.build(); } + + /** + * Listener for issues encountered while processing a valid pde parse tree. + */ + static interface PdeParseTreeErrorListener { + + /** + * Callback to invoke when an issue is encountered while processing a valid PDE parse tree. + * + * @param issue The issue reported. + */ + void onError(PdePreprocessIssue issue); + + } } diff --git a/java/src/processing/mode/java/preproc/PdePreprocessor.java b/java/src/processing/mode/java/preproc/PdePreprocessor.java index 44cfd2e5c..c4a04499c 100644 --- a/java/src/processing/mode/java/preproc/PdePreprocessor.java +++ b/java/src/processing/mode/java/preproc/PdePreprocessor.java @@ -25,6 +25,7 @@ import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.tree.ParseTree; @@ -38,52 +39,78 @@ import processing.mode.java.preproc.issue.PdePreprocessIssue; /** - * Utility to preprocess sketches prior to comilation. + * Utility to preprocess sketches prior to compilation. + * + *

+ * This preprocessor assists with + *

*/ public class PdePreprocessor { + /** + * The mode that the sketch uses to run. + */ public static enum Mode { - STATIC, ACTIVE, JAVA + /** + * Sketch without draw, setup, or settings functions where code is run as if the body of a + * method without any enclosing types. This code will not define its enclosing class or method. + */ + STATIC, + + /** + * Sketch using draw, setup, and / or settings where the code is run as if defining the body + * of a class. This code will not define its enclosing class but it will define its enclosing + * method. + */ + ACTIVE, + + /** + * Sketch written like typical Java where the code is run such that it defines the enclosing + * classes itself. + */ + JAVA } - private String sketchName; - private int tabSize; - - private boolean hasMain; - + private final String sketchName; + private final int tabSize; private final boolean isTesting; + private final ParseTreeListenerFactory listenerFactory; + + private boolean foundMain; /** - * Create a new preprocessor. + * Create a new PdePreprocessorBuilder for a sketch of the given name. * - * @param sketchName The name of the sketch. + * Create a new builder to help instantiate a preprocessor for a sketch of the given name. Use + * this builder to configure settings of the preprocessor before building. + * + * @param sketchName The name of the sketch for which a preprocessor will be built. + * @return Builder to create a preprocessor for the sketch of the given name. */ - public PdePreprocessor(final String sketchName) { - this(sketchName, Preferences.getInteger("editor.tabs.size"), false); + public static PdePreprocessorBuilder builderFor(String sketchName) { + return new PdePreprocessorBuilder(sketchName); } /** * Create a new preprocessor. * - * @param sketchName The name of the sketch. - * @param tabSize The number of tabs. - */ - public PdePreprocessor(final String sketchName, final int tabSize) { - this(sketchName, tabSize, false); - } - - /** - * Create a new preprocessor. + * Create a new preprocessor that will use the following set of configuration values to process + * a parse tree. This object should be instantiated by calling {builderFor}. * - * @param sketchName The name of the sketch. - * @param tabSize The number of tabs. - * @param isTesting Flag indicating if this is running in unit tests (true) or in production + * @param newSketchName The name of the sketch. + * @param newTabSize The number of spaces within a tab. + * @param newIsTesting Flag indicating if this is running in unit tests (true) or in production * (false). + * @param newFactory The factory to use for building the ANTLR tree traversal listener where + * preprocessing edits should be made. */ - public PdePreprocessor(final String sketchName, final int tabSize, boolean isTesting) { - this.sketchName = sketchName; - this.tabSize = tabSize; - this.isTesting = isTesting; + private PdePreprocessor(final String newSketchName, final int newTabSize, boolean newIsTesting, + final ParseTreeListenerFactory newFactory) { + + sketchName = newSketchName; + tabSize = newTabSize; + isTesting = newIsTesting; + listenerFactory = newFactory; } /** @@ -151,7 +178,7 @@ public class PdePreprocessor { // Parser final List preprocessIssues = new ArrayList<>(); final List treeIssues = new ArrayList<>(); - PdeParseTreeListener listener = createListener(tokens, sketchName); + PdeParseTreeListener listener = listenerFactory.build(tokens, sketchName, tabSize); listener.setTesting(isTesting); listener.setCoreImports(ImportUtil.getCoreImports()); listener.setDefaultImports(ImportUtil.getDefaultImports()); @@ -188,7 +215,7 @@ public class PdePreprocessor { PrintWriter outPrintWriter = new PrintWriter(outWriter); outPrintWriter.print(outputProgram); - hasMain = listener.foundMain(); + foundMain = listener.foundMain(); return listener.getResult(); } @@ -199,9 +226,133 @@ public class PdePreprocessor { * @return True if a main method was found. False otherwise. */ public boolean hasMain() { - return hasMain; + return foundMain; } + /* ======================== + * === Type Definitions === + * ======================== + * + * The preprocessor has a sizable number of parameters, including those that can modify its + * internal behavior. These supporting types help initialize this object and provide hooks for + * behavior modifications. + */ + + /** + * Builder to help instantiate a PdePreprocessor. + * + * The PdePreprocessor includes a number of parameters, including some behavioral parameters that + * change how the parse tree is processed. This builder helps instantiate this object by providing + * reasonable defaults for most values but allowing client to (especially modes) to override those + * defaults. + */ + public static class PdePreprocessorBuilder { + + private final String sketchName; + private Optional tabSize; + private Optional isTesting; + private Optional parseTreeFactory; + + private PdePreprocessorBuilder(String newSketchName) { + sketchName = newSketchName; + tabSize = Optional.empty(); + isTesting = Optional.empty(); + parseTreeFactory = Optional.empty(); + } + + /** + * Set how large the tabs should be. + * + * @param newTabSize The number of spaces in a tab. + * @return This builder for method chaining. + */ + public PdePreprocessorBuilder setTabSize(int newTabSize) { + tabSize = Optional.of(newTabSize); + return this; + } + + /** + * Indicate if this preprocessor is running within unittests. + * + * @param newIsTesting Flag that, if true, will configure the preprocessor to run safely within + * a unit testing environment. + * @return This builder for method chaining. + */ + public PdePreprocessorBuilder setIsTesting(boolean newIsTesting) { + isTesting = Optional.of(newIsTesting); + return this; + } + + /** + * Specify how the parse tree listener should be built. + * + * The ANTLR parse tree listener is where the preprocessing edits are generated and some client + * code (like modes) may need to override some of the preprocessing edit behavior. Specifying + * this factory allows client code to replace the default PdeParseTreeListener that is used + * during preprocessing. + * + * @param newFactory The factory to use in building a parse tree listener. + * @return This builder for method chaining. + */ + public PdePreprocessorBuilder setParseTreeFactory(ParseTreeListenerFactory newFactory) { + parseTreeFactory = Optional.of(newFactory); + return this; + } + + /** + * Build the preprocessor. + * + * @return Newly built preproceesor. + */ + public PdePreprocessor build() { + final int effectiveTabSize = tabSize.orElseGet( + () -> Preferences.getInteger("editor.tabs.size") + ); + + final boolean effectiveIsTesting = isTesting.orElse(false); + + ParseTreeListenerFactory effectiveFactory = parseTreeFactory.orElse( + PdeParseTreeListener::new + ); + + return new PdePreprocessor( + sketchName, + effectiveTabSize, + effectiveIsTesting, + effectiveFactory + ); + } + + } + + /** + * Factory which creates parse tree traversal listeners. + * + * The ANTLR parse tree listener is where the preprocessing edits are generated and some client + * code (like modes) may need to override some of the preprocessing edit behavior. Specifying + * this factory allows client code to replace the default PdeParseTreeListener that is used + * during preprocessing. + */ + public static interface ParseTreeListenerFactory { + + /** + * Create a new processing listener. + * + * @param tokens The token stream with sketch code contents. + * @param sketchName The name of the sketch that will be preprocessed. + * @param tabSize The size (number of spaces) of the tabs. + * @return The newly created listener. + */ + PdeParseTreeListener build(CommonTokenStream tokens, String sketchName, int tabSize); + + } + + + /* ================================== + * === Internal Utility Functions === + * ================================== + */ + /** * Factory function to create a {PdeParseTreeListener} for use in preprocessing * diff --git a/java/src/processing/mode/java/preproc/SourceEmitter.java b/java/src/processing/mode/java/preproc/SourceEmitter.java deleted file mode 100644 index 747a0106a..000000000 --- a/java/src/processing/mode/java/preproc/SourceEmitter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* -Part of the Processing project - http://processing.org - -Copyright (c) 2012-19 The Processing Foundation - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License version 2 -as published by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software Foundation, -Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package processing.mode.java.preproc; - - -/** - * Simple interface for strategy which can emit the full body of a processing sketch. - */ -public interface SourceEmitter { - - /** - * Get the full body of the processing sketch. - * - * @return String processing sketch source code across all tabs. - */ - String getSource(); - -} diff --git a/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java b/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java index ad736ed4a..d7f3acc75 100644 --- a/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java +++ b/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java @@ -25,8 +25,6 @@ import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; -import processing.mode.java.preproc.SourceEmitter; - import java.util.Optional; @@ -100,4 +98,31 @@ public class PdeIssueEmitter extends BaseErrorListener { )); } + /** + * Simple interface for strategy which can emit the full body of a processing sketch. + */ + public static interface SourceEmitter { + + /** + * Get the full body of the processing sketch. + * + * @return String processing sketch source code across all tabs. + */ + String getSource(); + + } + + /** + * Interface for listener that responds to issues reported by the preprocessor. + */ + public static interface PdePreprocessIssueListener { + + /** + * Callback to invoke when an issue is encountered in preprocesing. + * + * @param issue Description of the issue. + */ + void onIssue(PdePreprocessIssue issue); + + } } diff --git a/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java deleted file mode 100644 index 2f0e46d65..000000000 --- a/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package processing.mode.java.preproc.issue; - -import processing.mode.java.preproc.issue.PdePreprocessIssue; - -public interface PdePreprocessIssueListener { - - void onIssue(PdePreprocessIssue issue); - -} diff --git a/java/test/processing/mode/java/ProcessingTestUtil.java b/java/test/processing/mode/java/ProcessingTestUtil.java index 6e113e9d2..723329c5b 100644 --- a/java/test/processing/mode/java/ProcessingTestUtil.java +++ b/java/test/processing/mode/java/ProcessingTestUtil.java @@ -39,7 +39,13 @@ public class ProcessingTestUtil { throws SketchException { final String program = read(resource); final StringWriter out = new StringWriter(); - PreprocessorResult result = new PdePreprocessor(name, 4, true).write(out, program); + + PdePreprocessor preprocessor = PdePreprocessor.builderFor(name) + .setTabSize(4) + .setIsTesting(true) + .build(); + + PreprocessorResult result = preprocessor.write(out, program); if (result.getPreprocessIssues().size() > 0) { throw new PdePreprocessIssueException(result.getPreprocessIssues().get(0));