2005-08-25 23:06:28 +02:00
|
|
|
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
/*
|
|
|
|
PdePreprocessor - wrapper for default ANTLR-generated parser
|
2005-09-25 16:11:32 +02:00
|
|
|
Part of the Wiring project - http://wiring.org.co
|
2005-08-25 23:06:28 +02:00
|
|
|
|
2005-09-25 16:11:32 +02:00
|
|
|
Copyright (c) 2004-05 Hernando Barragan
|
|
|
|
|
|
|
|
Processing version Copyright (c) 2004-05 Ben Fry and Casey Reas
|
2005-08-25 23:06:28 +02:00
|
|
|
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
|
|
|
|
|
|
|
ANTLR-generated parser and several supporting classes written
|
|
|
|
by Dan Mosedale via funding from the Interaction Institute IVREA.
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
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.app.preproc;
|
|
|
|
|
|
|
|
import processing.app.*;
|
2007-09-25 16:04:01 +02:00
|
|
|
import processing.core.*;
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
import java.io.*;
|
2008-02-16 20:34:26 +01:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
import antlr.*;
|
|
|
|
import antlr.collections.*;
|
|
|
|
import antlr.collections.impl.*;
|
|
|
|
|
|
|
|
import com.oroinc.text.regex.*;
|
|
|
|
|
|
|
|
|
|
|
|
public class PdePreprocessor {
|
|
|
|
|
|
|
|
static final int JDK11 = 0;
|
|
|
|
static final int JDK13 = 1;
|
|
|
|
static final int JDK14 = 2;
|
|
|
|
|
2008-02-16 20:34:26 +01:00
|
|
|
//static String defaultImports[][] = new String[3][];
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
// these ones have the .* at the end, since a class name
|
|
|
|
// might be at the end instead of .* whcih would make trouble
|
|
|
|
// other classes using this can lop of the . and anything after
|
|
|
|
// it to produce a package name consistently.
|
|
|
|
public String extraImports[];
|
|
|
|
|
|
|
|
static public final int STATIC = 0; // formerly BEGINNER
|
|
|
|
static public final int ACTIVE = 1; // formerly INTERMEDIATE
|
|
|
|
static public final int JAVA = 2; // formerly ADVANCED
|
|
|
|
// static to make it easier for the antlr preproc to get at it
|
|
|
|
static public int programType = -1;
|
|
|
|
|
|
|
|
Reader programReader;
|
|
|
|
String buildPath;
|
|
|
|
|
2005-09-25 16:11:32 +02:00
|
|
|
// stores number of built user-defined function prototypes
|
|
|
|
public int prototypeCount = 0;
|
2005-08-25 23:06:28 +02:00
|
|
|
|
2006-03-26 21:12:53 +02:00
|
|
|
// stores number of included library headers written
|
|
|
|
public int headerCount = 0;
|
|
|
|
|
2005-08-25 23:06:28 +02:00
|
|
|
/**
|
|
|
|
* These may change in-between (if the prefs panel adds this option)
|
|
|
|
* so grab them here on construction.
|
|
|
|
*/
|
2008-02-16 20:34:26 +01:00
|
|
|
public PdePreprocessor() {}
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Used by PdeEmitter.dumpHiddenTokens()
|
|
|
|
*/
|
2005-09-25 16:11:32 +02:00
|
|
|
//public static TokenStreamCopyingHiddenTokenFilter filter;
|
2008-02-16 20:34:26 +01:00
|
|
|
|
2008-02-20 03:27:23 +01:00
|
|
|
/**
|
|
|
|
* Returns the index of the first character that's not whitespace, a comment
|
|
|
|
* or a pre-processor directive.
|
|
|
|
*/
|
|
|
|
public int firstStatement(String in) {
|
|
|
|
PatternMatcherInput input = new PatternMatcherInput(in);
|
|
|
|
PatternCompiler compiler = new Perl5Compiler();
|
|
|
|
PatternMatcher matcher = new Perl5Matcher();
|
|
|
|
Pattern pattern = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
pattern = compiler.compile(
|
|
|
|
// XXX: doesn't properly handle special single-quoted characters
|
|
|
|
// whitespace
|
|
|
|
"\\s+" + "|" +
|
|
|
|
// multi-line comment
|
2008-03-15 18:49:14 +01:00
|
|
|
"(/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)" + "|" +
|
2008-02-20 03:27:23 +01:00
|
|
|
// single-line comment
|
|
|
|
"(//.*?$)" + "|" +
|
|
|
|
// pre-processor directive
|
|
|
|
"(#(?:\\\\\\n|.)*)",
|
|
|
|
Perl5Compiler.MULTILINE_MASK);
|
|
|
|
} catch (MalformedPatternException e) {
|
|
|
|
throw new RuntimeException("Internal error in firstStatement()", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
while (matcher.matchesPrefix(input, pattern)) {
|
|
|
|
i = matcher.getMatch().endOffset(0);
|
|
|
|
input.setCurrentOffset(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2008-02-16 20:34:26 +01:00
|
|
|
/**
|
|
|
|
* Strips comments, pre-processor directives, single- and double-quoted
|
|
|
|
* strings from a string.
|
|
|
|
* @param in the String to strip
|
|
|
|
* @return the stripped String
|
|
|
|
*/
|
|
|
|
public String strip(String in) throws MalformedPatternException {
|
|
|
|
PatternCompiler compiler = new Perl5Compiler();
|
|
|
|
PatternMatcher matcher = new Perl5Matcher();
|
|
|
|
Pattern pattern = compiler.compile(
|
|
|
|
// XXX: doesn't properly handle special single-quoted characters
|
|
|
|
// single-quoted character
|
|
|
|
"('.')" + "|" +
|
|
|
|
// double-quoted string
|
|
|
|
"(\"(?:[^\"\\\\]|\\\\.)*\")" + "|" +
|
|
|
|
// multi-line comment
|
2008-03-15 18:49:14 +01:00
|
|
|
"(/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)" + "|" +
|
2008-02-16 20:34:26 +01:00
|
|
|
// single-line comment
|
|
|
|
"(//.*?$)" + "|" +
|
|
|
|
// pre-processor directive
|
|
|
|
"(^\\s*#.*?$)",
|
|
|
|
Perl5Compiler.MULTILINE_MASK);
|
|
|
|
|
|
|
|
while (matcher.contains(in, pattern)) {
|
|
|
|
MatchResult result = matcher.getMatch();
|
|
|
|
// XXX: should preserve newlines in the result so that line numbers of
|
|
|
|
// the stripped string correspond to those in the original source.
|
|
|
|
in = in.substring(0, result.beginOffset(0)) + " " + in.substring(result.endOffset(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
return in;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the contents of all top-level curly brace pairs {}.
|
|
|
|
* @param in the String to collapse
|
|
|
|
* @return the collapsed String
|
|
|
|
*/
|
|
|
|
private String collapseBraces(String in) {
|
|
|
|
StringBuffer buffer = new StringBuffer();
|
|
|
|
int nesting = 0;
|
|
|
|
int start = 0;
|
|
|
|
|
|
|
|
// XXX: need to keep newlines inside braces so we can determine the line
|
|
|
|
// number of a prototype
|
|
|
|
for (int i = 0; i < in.length(); i++) {
|
|
|
|
if (in.charAt(i) == '{') {
|
|
|
|
if (nesting == 0) {
|
|
|
|
buffer.append(in.substring(start, i + 1)); // include the '{'
|
|
|
|
}
|
|
|
|
nesting++;
|
|
|
|
}
|
|
|
|
if (in.charAt(i) == '}') {
|
|
|
|
nesting--;
|
|
|
|
if (nesting == 0) {
|
|
|
|
start = i; // include the '}'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.append(in.substring(start));
|
|
|
|
|
|
|
|
return buffer.toString();
|
|
|
|
}
|
|
|
|
|
2008-03-15 18:49:14 +01:00
|
|
|
public List prototypes(String in) throws MalformedPatternException {
|
2008-02-16 20:34:26 +01:00
|
|
|
in = collapseBraces(strip(in));
|
|
|
|
|
|
|
|
PatternMatcherInput input = new PatternMatcherInput(in);
|
|
|
|
PatternCompiler compiler = new Perl5Compiler();
|
|
|
|
PatternMatcher matcher = new Perl5Matcher();
|
|
|
|
// XXX: doesn't handle ... varargs
|
|
|
|
// XXX: doesn't handle function pointers
|
|
|
|
Pattern pattern = compiler.compile(
|
|
|
|
"[\\w\\[\\]\\*]+\\s+[\\[\\]\\*\\w\\s]+\\([,\\[\\]\\*\\w\\s]*\\)(?=\\s*\\{)");
|
|
|
|
List matches = new ArrayList();
|
|
|
|
|
|
|
|
while (matcher.contains(input, pattern)) {
|
|
|
|
matches.add(matcher.getMatch().group(0) + ";");
|
|
|
|
}
|
|
|
|
|
|
|
|
return matches;
|
|
|
|
}
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* preprocesses a pde file and write out a java file
|
2007-09-25 16:04:01 +02:00
|
|
|
* @param pretty true if should also space out/indent lines
|
2005-08-25 23:06:28 +02:00
|
|
|
* @return the classname of the exported Java
|
|
|
|
*/
|
|
|
|
//public String write(String program, String buildPath, String name,
|
|
|
|
// String extraImports[]) throws java.lang.Exception {
|
|
|
|
public String write(String program, String buildPath,
|
2007-01-12 18:58:39 +01:00
|
|
|
String name, String codeFolderPackages[],
|
2008-02-16 20:34:26 +01:00
|
|
|
Target target)
|
2005-08-25 23:06:28 +02:00
|
|
|
throws java.lang.Exception {
|
|
|
|
// if the program ends with no CR or LF an OutOfMemoryError will happen.
|
|
|
|
// not gonna track down the bug now, so here's a hack for it:
|
2007-09-25 16:04:01 +02:00
|
|
|
// bug filed at http://dev.processing.org/bugs/show_bug.cgi?id=5
|
|
|
|
//if ((program.length() > 0) &&
|
|
|
|
//program.charAt(program.length()-1) != '\n') {
|
2005-08-25 23:06:28 +02:00
|
|
|
program += "\n";
|
2007-09-25 16:04:01 +02:00
|
|
|
//}
|
|
|
|
|
|
|
|
// if the program ends with an unterminated multiline comment,
|
|
|
|
// an OutOfMemoryError or NullPointerException will happen.
|
|
|
|
// again, not gonna bother tracking this down, but here's a hack.
|
|
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=16
|
|
|
|
Sketch.scrubComments(program);
|
|
|
|
// this returns the scrubbed version, but more important for this
|
|
|
|
// function, it'll check to see if there are errors with the comments.
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
if (Preferences.getBoolean("preproc.substitute_unicode")) {
|
|
|
|
// check for non-ascii chars (these will be/must be in unicode format)
|
|
|
|
char p[] = program.toCharArray();
|
|
|
|
int unicodeCount = 0;
|
|
|
|
for (int i = 0; i < p.length; i++) {
|
|
|
|
if (p[i] > 127) unicodeCount++;
|
|
|
|
}
|
|
|
|
// if non-ascii chars are in there, convert to unicode escapes
|
|
|
|
if (unicodeCount != 0) {
|
|
|
|
// add unicodeCount * 5.. replacing each unicode char
|
|
|
|
// with six digit uXXXX sequence (xxxx is in hex)
|
|
|
|
// (except for nbsp chars which will be a replaced with a space)
|
|
|
|
int index = 0;
|
|
|
|
char p2[] = new char[p.length + unicodeCount*5];
|
|
|
|
for (int i = 0; i < p.length; i++) {
|
|
|
|
if (p[i] < 128) {
|
|
|
|
p2[index++] = p[i];
|
|
|
|
|
|
|
|
} else if (p[i] == 160) { // unicode for non-breaking space
|
|
|
|
p2[index++] = ' ';
|
|
|
|
|
|
|
|
} else {
|
|
|
|
int c = p[i];
|
|
|
|
p2[index++] = '\\';
|
|
|
|
p2[index++] = 'u';
|
|
|
|
char str[] = Integer.toHexString(c).toCharArray();
|
|
|
|
// add leading zeros, so that the length is 4
|
|
|
|
//for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0';
|
|
|
|
for (int m = 0; m < 4 - str.length; m++) p2[index++] = '0';
|
|
|
|
System.arraycopy(str, 0, p2, index, str.length);
|
|
|
|
index += str.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
program = new String(p2, 0, index);
|
|
|
|
}
|
|
|
|
}
|
2008-02-16 20:34:26 +01:00
|
|
|
|
2005-08-25 23:06:28 +02:00
|
|
|
// if this guy has his own imports, need to remove them
|
|
|
|
// just in case it's not an advanced mode sketch
|
|
|
|
PatternMatcher matcher = new Perl5Matcher();
|
|
|
|
PatternCompiler compiler = new Perl5Compiler();
|
|
|
|
//String mess = "^\\s*(import\\s+\\S+\\s*;)";
|
2006-03-27 00:16:55 +02:00
|
|
|
//String mess = "^\\s*(import\\s+)(\\S+)(\\s*;)";
|
2008-02-16 20:34:26 +01:00
|
|
|
String mess = "^\\s*#include\\s+[<\"](\\S+)[\">]";
|
2005-08-25 23:06:28 +02:00
|
|
|
java.util.Vector imports = new java.util.Vector();
|
|
|
|
|
|
|
|
Pattern pattern = null;
|
|
|
|
try {
|
|
|
|
pattern = compiler.compile(mess);
|
|
|
|
} catch (MalformedPatternException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2008-02-16 20:34:26 +01:00
|
|
|
PatternMatcherInput input = new PatternMatcherInput(program);
|
|
|
|
while (matcher.contains(input, pattern)) {
|
|
|
|
imports.add(matcher.getMatch().group(1));
|
|
|
|
}
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
extraImports = new String[imports.size()];
|
|
|
|
imports.copyInto(extraImports);
|
|
|
|
|
|
|
|
// do this after the program gets re-combobulated
|
|
|
|
this.programReader = new StringReader(program);
|
|
|
|
this.buildPath = buildPath;
|
2005-09-25 16:11:32 +02:00
|
|
|
|
2008-02-16 20:34:26 +01:00
|
|
|
List prototypes = prototypes(program);
|
|
|
|
|
2005-09-25 16:11:32 +02:00
|
|
|
// store # of prototypes so that line number reporting can be adjusted
|
|
|
|
prototypeCount = prototypes.size();
|
|
|
|
|
2005-08-25 23:06:28 +02:00
|
|
|
if (name == null) return null;
|
|
|
|
|
|
|
|
// output the code
|
2006-02-24 16:52:58 +01:00
|
|
|
File streamFile = new File(buildPath, name + ".cpp");
|
2005-08-25 23:06:28 +02:00
|
|
|
PrintStream stream = new PrintStream(new FileOutputStream(streamFile));
|
|
|
|
|
2008-02-20 03:27:23 +01:00
|
|
|
writeHeader(stream);
|
2006-02-24 16:52:58 +01:00
|
|
|
//added to write the pde code to the cpp file
|
2008-02-20 03:27:23 +01:00
|
|
|
writeProgram(stream, program, prototypes);
|
2007-01-12 18:58:39 +01:00
|
|
|
writeFooter(stream, target);
|
2005-08-25 23:06:28 +02:00
|
|
|
stream.close();
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2006-02-24 16:52:58 +01:00
|
|
|
// Write the pde program to the cpp file
|
2008-02-20 03:27:23 +01:00
|
|
|
void writeProgram(PrintStream out, String program, List prototypes) {
|
|
|
|
int prototypeInsertionPoint = firstStatement(program);
|
|
|
|
|
|
|
|
out.print(program.substring(0, prototypeInsertionPoint));
|
2008-05-07 20:25:40 +02:00
|
|
|
out.print("#include \"WProgram.h\"\n");
|
2008-02-20 03:27:23 +01:00
|
|
|
|
|
|
|
// print user defined prototypes
|
|
|
|
for (int i = 0; i < prototypes.size(); i++) {
|
|
|
|
out.print(prototypes.get(i) + "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
out.print(program.substring(prototypeInsertionPoint));
|
2006-02-24 16:52:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-08-25 23:06:28 +02:00
|
|
|
/**
|
|
|
|
* Write any required header material (eg imports, class decl stuff)
|
|
|
|
*
|
|
|
|
* @param out PrintStream to write it to.
|
|
|
|
*/
|
2008-05-07 20:25:40 +02:00
|
|
|
void writeHeader(PrintStream out) throws IOException {}
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Write any necessary closing text.
|
|
|
|
*
|
|
|
|
* @param out PrintStream to write it to.
|
|
|
|
*/
|
2007-01-12 18:58:39 +01:00
|
|
|
void writeFooter(PrintStream out, Target target) throws java.lang.Exception {
|
|
|
|
// Open the file main.cxx and copy its entire contents to the bottom of the
|
|
|
|
// generated sketch .cpp file...
|
2005-08-25 23:06:28 +02:00
|
|
|
|
2007-01-12 18:58:39 +01:00
|
|
|
String mainFileName = target.getPath() + File.separator + "main.cxx";
|
|
|
|
FileReader reader = null;
|
|
|
|
reader = new FileReader(mainFileName);
|
|
|
|
|
|
|
|
LineNumberReader mainfile = new LineNumberReader(reader);
|
2005-08-25 23:06:28 +02:00
|
|
|
|
2007-01-12 18:58:39 +01:00
|
|
|
String line;
|
|
|
|
while ((line = mainfile.readLine()) != null) {
|
|
|
|
out.print(line + "\n");
|
2005-08-25 23:06:28 +02:00
|
|
|
}
|
2007-01-12 18:58:39 +01:00
|
|
|
|
|
|
|
mainfile.close();
|
|
|
|
}
|
2005-08-25 23:06:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
static String advClassName = "";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the first CLASS_DEF node in the tree, and return the name of the
|
|
|
|
* class in question.
|
|
|
|
*
|
|
|
|
* XXXdmose right now, we're using a little hack to the grammar to get
|
|
|
|
* this info. In fact, we should be descending the AST passed in.
|
|
|
|
*/
|
|
|
|
String getFirstClassName(AST ast) {
|
|
|
|
|
|
|
|
String t = advClassName;
|
|
|
|
advClassName = "";
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|