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.
This commit is contained in:
A Pottinger
2019-10-10 08:36:34 -07:00
parent 54bdd37c88
commit 5faf2a10a6
9 changed files with 232 additions and 120 deletions

View File

@@ -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);
}

View File

@@ -125,7 +125,7 @@ public class JavaEditor extends Editor {
public PdePreprocessor createPreprocessor(final String sketchName) {
return new PdePreprocessor(sketchName);
return PdePreprocessor.builderFor(sketchName).build();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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.
*
* <p>
* This preprocessor assists with
* </p>
*/
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<PdePreprocessIssue> preprocessIssues = new ArrayList<>();
final List<PdePreprocessIssue> 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<Integer> tabSize;
private Optional<Boolean> isTesting;
private Optional<ParseTreeListenerFactory> 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
*

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -1,9 +0,0 @@
package processing.mode.java.preproc.issue;
import processing.mode.java.preproc.issue.PdePreprocessIssue;
public interface PdePreprocessIssueListener {
void onIssue(PdePreprocessIssue issue);
}