/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
PdePreprocessor - wrapper for default ANTLR-generated parser
Part of the Wiring project - http://wiring.org.co
Copyright (c) 2004-11 Hernando Barragan
Processing version Copyright (c) 2004-05 Ben Fry and Casey Reas
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.*;
import processing.core.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
/**
* Class that orchestrates preprocessing p5 syntax into straight Java.
*/
public class PdePreprocessor {
// stores number of built user-defined function prototypes
public int prototypeCount = 0;
// stores number of included library headers written
// we always write two header: WProgram.h
public int headerCount = 1;
List<String> prototypes;
// these ones have the .* at the end, since a class name might be at the end
// instead of .* which would make trouble other classes using this can lop
// off the . and anything after it to produce a package name consistently.
//public String extraImports[];
List<String> programImports;
// imports just from the code folder, treated differently
// than the others, since the imports are auto-generated.
List<String> codeFolderImports;
String indent;
PrintStream stream;
String program;
// Reader programReader;
String buildPath;
String name;
/**
* Setup a new preprocessor.
*/
public PdePreprocessor() {
int tabSize = Preferences.getInteger("editor.tabs.size");
char[] indentChars = new char[tabSize];
Arrays.fill(indentChars, ' ');
indent = new String(indentChars);
}
public int writePrefix(String program, String buildPath,
String sketchName, String codeFolderPackages[]) throws FileNotFoundException {
this.buildPath = buildPath;
this.name = sketchName;
// 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:
// http://dev.processing.org/bugs/show_bug.cgi?id=5
program += "\n";
// if the program ends with an unterminated multi-line 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);
// If there are errors, an exception is thrown and this fxn exits.
if (Preferences.getBoolean("preproc.substitute_unicode")) {
program = substituteUnicode(program);
}
//String importRegexp = "(?:^|\\s|;)(import\\s+)(\\S+)(\\s*;)";
String importRegexp = "^\\s*#include\\s+[<\"](\\S+)[\">]";
programImports = new ArrayList<String>();
String[][] pieces = PApplet.matchAll(program, importRegexp);
if (pieces != null)
for (int i = 0; i < pieces.length; i++)
programImports.add(pieces[i][1]); // the package name
// hack to get the serial working without the #include
//programImports.add("HardwareSerial.h");
codeFolderImports = new ArrayList<String>();
// if (codeFolderPackages != null) {
// for (String item : codeFolderPackages) {
// codeFolderImports.add(item + ".*");
// }
// }
prototypes = prototypes(program);
// store # of prototypes so that line number reporting can be adjusted
prototypeCount = prototypes.size();
// do this after the program gets re-combobulated
this.program = program;
// output the code
File streamFile = new File(buildPath, name + ".cpp");
stream = new PrintStream(new FileOutputStream(streamFile));
return headerCount + prototypeCount;
}
static String substituteUnicode(String program) {
// 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);
}
return program;
}
/**
* preprocesses a pde file and write out a java file
* @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() throws java.lang.Exception {
writeProgram(stream, program, prototypes);
writeFooter(stream);
stream.close();
return name;
}
// Write the pde program to the cpp file
protected void writeProgram(PrintStream out, String program, List<String> prototypes) {
int prototypeInsertionPoint = firstStatement(program);
out.print(program.substring(0, prototypeInsertionPoint));
out.print("#include \"WProgram.h\"\n");
//out.print("#include <HardwareSerial.h>\n");
// print user defined prototypes
for (int i = 0; i < prototypes.size(); i++) {
out.print(prototypes.get(i) + "\n");
}
out.print(program.substring(prototypeInsertionPoint));
}
/**
* Write any necessary closing text.
*
* @param out PrintStream to write it to.
*/
protected void writeFooter(PrintStream out) throws java.lang.Exception {}
public List<String> getExtraImports() {
return programImports;
}
/**
* Returns the index of the first character that's not whitespace, a comment
* or a pre-processor directive.
*/
public int firstStatement(String in) {
// whitespace
String p = "\\s+";
// multi-line and single-line comment
//p += "|" + "(//\\s*?$)|(/\\*\\s*?\\*/)";
p += "|(/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)|(//.*?$)";
// pre-processor directive
p += "|(#(?:\\\\\\n|.)*)";
Pattern pattern = Pattern.compile(p, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(in);
int i = 0;
while (matcher.find()) {
if (matcher.start()!=i)
break;
i = matcher.end();
}
return i;
}
/**
* 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) {
// XXX: doesn't properly handle special single-quoted characters
// single-quoted character
String p = "('.')";
// double-quoted string
p += "|(\"(?:[^\"\\\\]|\\\\.)*\")";
// single and multi-line comment
//p += "|" + "(//\\s*?$)|(/\\*\\s*?\\*/)";
p += "|(//.*?$)|(/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)";
// pre-processor directive
p += "|" + "(^\\s*#.*?$)";
Pattern pattern = Pattern.compile(p, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(in);
return matcher.replaceAll(" ");
}
/**
* 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();
}
public ArrayList<String> prototypes(String in) {
in = collapseBraces(strip(in));
// XXX: doesn't handle ... varargs
// XXX: doesn't handle function pointers
Pattern pattern = Pattern.compile("[\\w\\[\\]\\*]+\\s+[&\\[\\]\\*\\w\\s]+\\([&,\\[\\]\\*\\w\\s]*\\)(?=\\s*\\{)");
ArrayList<String> matches = new ArrayList<String>();
Matcher matcher = pattern.matcher(in);
while (matcher.find())
if (!matches.contains(matcher.group(0) + ";"))
matches.add(matcher.group(0) + ";");
return matches;
}
}