diff options
Diffstat (limited to 'src/java/com/jogamp/gluegen/pcpp')
-rwxr-xr-x | src/java/com/jogamp/gluegen/pcpp/ConcatenatingReader.java | 181 | ||||
-rw-r--r-- | src/java/com/jogamp/gluegen/pcpp/PCPP.java | 1107 |
2 files changed, 1288 insertions, 0 deletions
diff --git a/src/java/com/jogamp/gluegen/pcpp/ConcatenatingReader.java b/src/java/com/jogamp/gluegen/pcpp/ConcatenatingReader.java new file mode 100755 index 0000000..1eea281 --- /dev/null +++ b/src/java/com/jogamp/gluegen/pcpp/ConcatenatingReader.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.jogamp.gluegen.pcpp; + +import java.io.*; + +/** A Reader implementation which finds lines ending in the backslash + character ('\') and concatenates them with the next line. */ + +public class ConcatenatingReader extends FilterReader { + // Any leftover characters go here + private char[] curBuf; + private int curPos; + private BufferedReader reader; + private static String newline = System.getProperty("line.separator"); + + /** This class requires that the input reader be a BufferedReader so + it can do line-oriented operations. */ + public ConcatenatingReader(BufferedReader in) { + super(in); + this.reader = in; + } + + @Override + public int read() throws IOException { + char[] tmp = new char[1]; + int num = read(tmp, 0, 1); + if (num < 0) + return -1; + return tmp[0]; + } + + // It's easier not to support mark/reset since we don't need it + @Override + public boolean markSupported() { + return false; + } + + @Override + public void mark(int readAheadLimit) throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public boolean ready() throws IOException { + if (curBuf != null || reader.ready()) + return true; + return false; + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + if (curBuf == null) { + nextLine(); + } + + if (curBuf == null) { + return -1; + } + + int numRead = 0; + + while ((len > 0) && (curBuf != null) && (curPos < curBuf.length)) { + cbuf[off] = curBuf[curPos]; + ++curPos; + ++off; + --len; + ++numRead; + if (curPos == curBuf.length) { + nextLine(); + } + } + + return numRead; + } + + @Override + public long skip(long n) throws IOException { + long numSkipped = 0; + + while (n > 0) { + int intN = (int) n; + char[] tmp = new char[intN]; + int numRead = read(tmp, 0, intN); + n -= numRead; + numSkipped += numRead; + if (numRead < intN) + break; + } + return numSkipped; + } + + private void nextLine() throws IOException { + String cur = reader.readLine(); + if (cur == null) { + curBuf = null; + return; + } + // The trailing newline was trimmed by the readLine() method. See + // whether we have to put it back or not, depending on whether the + // last character of the line is the concatenation character. + int numChars = cur.length(); + boolean needNewline = true; + if ((numChars > 0) && + (cur.charAt(cur.length() - 1) == '\\')) { + --numChars; + needNewline = false; + } + char[] buf = new char[numChars + (needNewline ? newline.length() : 0)]; + cur.getChars(0, numChars, buf, 0); + if (needNewline) { + newline.getChars(0, newline.length(), buf, numChars); + } + curBuf = buf; + curPos = 0; + } + + // Test harness + /* + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.out.println("Usage: java ConcatenatingReader [file name]"); + System.exit(1); + } + + ConcatenatingReader reader = new ConcatenatingReader(new BufferedReader(new FileReader(args[0]))); + OutputStreamWriter writer = new OutputStreamWriter(System.out); + char[] buf = new char[8192]; + boolean done = false; + while (!done && reader.ready()) { + int numRead = reader.read(buf, 0, buf.length); + writer.write(buf, 0, numRead); + if (numRead < buf.length) + done = true; + } + writer.flush(); + } + */ +} diff --git a/src/java/com/jogamp/gluegen/pcpp/PCPP.java b/src/java/com/jogamp/gluegen/pcpp/PCPP.java new file mode 100644 index 0000000..ab2c8b2 --- /dev/null +++ b/src/java/com/jogamp/gluegen/pcpp/PCPP.java @@ -0,0 +1,1107 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution 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. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.jogamp.gluegen.pcpp; + + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StreamTokenizer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import static java.util.logging.Level.*; + +/** A minimal pseudo-C-preprocessor designed in particular to preserve + #define statements defining constants so they can be observed by a + glue code generator. */ + +public class PCPP { + + private static final Logger LOG = Logger.getLogger(PCPP.class.getPackage().getName()); + + /** Map containing the results of #define statements. We must + evaluate certain very simple definitions (to properly handle + OpenGL's gl.h) but preserve the text of definitions evaluating + to constants. Macros and multi-line defines (which typically + contain either macro definitions or expressions) are currently + not handled. */ + private Map<String, String> defineMap = new HashMap<String, String>(128); + private Map<String, Macro> macroMap = new HashMap<String, Macro>(128); + private Set<String> nonConstantDefines = new HashSet<String>(128); + + /** List containing the #include paths as Strings */ + private List<String> includePaths; + + private ParseState state; + + private boolean enableDebugPrint; + + public PCPP(List<String> includePaths, boolean debug) { + this.includePaths = includePaths; + setOut(System.out); + enableDebugPrint = debug; + } + + public void run(Reader reader, String filename) throws IOException { + StreamTokenizer tok = null; + BufferedReader bufReader = null; + if (reader instanceof BufferedReader) { + bufReader = (BufferedReader) reader; + } else { + bufReader = new BufferedReader(reader); + } + + tok = new StreamTokenizer(new ConcatenatingReader(bufReader)); + initTokenizer(tok); + + ParseState curState = new ParseState(tok, filename); + ParseState oldState = state; + state = curState; + lineDirective(); + parse(); + state = oldState; + if (state != null) { + lineDirective(); + } + } + + private void initTokenizer(StreamTokenizer tok) { + tok.resetSyntax(); + tok.wordChars('a', 'z'); + tok.wordChars('A', 'Z'); + tok.wordChars('0', '9'); + tok.wordChars('_', '_'); + tok.wordChars('-', '.'); + tok.wordChars(128, 255); + tok.whitespaceChars(0, ' '); + tok.quoteChar('"'); + tok.quoteChar('\''); + tok.eolIsSignificant(true); + tok.slashSlashComments(true); + tok.slashStarComments(true); + } + + public String findFile(String filename) { + String sep = File.separator; + for (String inclPath : includePaths) { + String fullPath = inclPath + sep + filename; + File file = new File(fullPath); + if (file.exists()) { + return fullPath; + } + } + return null; + } + + public OutputStream out() { + return out; + } + + public void setOut(OutputStream out) { + this.out = out; + writer = new PrintWriter(out); + } + + // State + static class ParseState { + + private StreamTokenizer tok; + private String filename; + private boolean startOfLine; + private boolean startOfFile; + + ParseState(StreamTokenizer tok, String filename) { + this.tok = tok; + this.filename = filename; + startOfLine = true; + startOfFile = true; + } + + void pushBackToken() throws IOException { + tok.pushBack(); + } + + int curToken() { + return tok.ttype; + } + + int nextToken() throws IOException { + return tok.nextToken(); + } + + String curWord() { + return tok.sval; + } + + String filename() { + return filename; + } + + int lineNumber() { + return tok.lineno(); + } + + boolean startOfLine() { + return startOfLine; + } + + void setStartOfLine(boolean val) { + startOfLine = val; + } + + boolean startOfFile() { + return startOfFile; + } + + void setStartOfFile(boolean val) { + startOfFile = val; + } + + } + + private static class Macro { + + private final List<String> values; + private final List<String> params; + + Macro(List<String> params, List<String> values) { + this.values = values; + this.params = params; + } + + @Override + public String toString() { + return "params: "+params+" values: "+values; + } + + } + + // Accessors + + /** Equivalent to nextToken(false) */ + private int nextToken() throws IOException { + return nextToken(false); + } + + private int nextToken(boolean returnEOLs) throws IOException { + int lineno = lineNumber(); + // Check to see whether the previous call to nextToken() left an + // EOL on the stream + if (state.curToken() == StreamTokenizer.TT_EOL) { + state.setStartOfLine(true); + } else if (!state.startOfFile()) { + state.setStartOfLine(false); + } + state.setStartOfFile(false); + int val = state.nextToken(); + if (!returnEOLs) { + if (val == StreamTokenizer.TT_EOL) { + do { + // Consume and return next token, setting state appropriately + val = state.nextToken(); + state.setStartOfLine(true); + println(); + } while (val == StreamTokenizer.TT_EOL); + } + } + if (lineNumber() > lineno + 1) { + // This is a little noisier than it needs to be, but does handle + // the case of multi-line comments properly + lineDirective(); + } + return val; + } + + /** + * Reads the next token and throws an IOException if it is not the specified + * token character. + */ + private void nextRequiredToken(int requiredToken) throws IOException { + int nextTok = nextToken(); + if (nextTok != requiredToken) { + String msg = "Expected token '" + requiredToken + "' but got "; + switch (nextTok) { + case StreamTokenizer.TT_EOF: msg += "<EOF>"; break; + case StreamTokenizer.TT_EOL: msg += "<EOL>"; break; + default: msg += "'" + curTokenAsString() + "'"; break; + } + msg += " at file " + filename() + ", line " + lineNumber(); + throw new IOException(msg); + } + } + + + private String curTokenAsString() { + int t = state.curToken(); + if (t == StreamTokenizer.TT_WORD) { + return state.curWord(); + } + if (t == StreamTokenizer.TT_EOL) { + throw new RuntimeException("Should not be converting EOL characters to strings"); + } + char c = (char) t; + if (c == '"' || c == '\'') { + StringBuilder sb = new StringBuilder(); + sb.append(c); + sb.append(state.curWord()); + sb.append(c); + return sb.toString(); + } + return new String(new char[] { c }); + } + + private String nextWordOrString() throws IOException { + nextToken(); + return curTokenAsString(); + } + + private String nextWord() throws IOException { + int val = nextToken(); + if (val != StreamTokenizer.TT_WORD) { + throw new RuntimeException("Expected word at file " + filename() + + ", line " + lineNumber()); + } + return state.curWord(); + } + + private boolean startOfLine() { + return state.startOfLine(); + } + + private String filename() { + return state.filename(); + } + + private int lineNumber() { + return state.lineNumber(); + } + + ///////////// + // Parsing // + ///////////// + + private void parse() throws IOException { + int tok = 0; + while ((tok = nextToken()) != StreamTokenizer.TT_EOF) { + // A '#' at the beginning of a line is a preprocessor directive + if (startOfLine() && (tok == '#')) { + preprocessorDirective(); + } else { + // Output white space plus current token, handling #defines + // (though not properly -- only handling #defines to constants and the empty string) + + // !!HACK!! - print space only for word tokens. This way multicharacter + // operators such as ==, != etc. are property printed. + if (tok == StreamTokenizer.TT_WORD) { + print(" "); + } + String s = curTokenAsString(); + String newS = defineMap.get(s); + if (newS == null) { + newS = s; + } + + Macro macro = macroMap.get(newS); + if(macro != null) { + newS = ""; + List<String> args = new ArrayList<String>(); + while (nextToken() != StreamTokenizer.TT_EOL) { + String token = curTokenAsString(); + if(")".equals(token)) { + break; + }else if(!",".equals(token) && !"(".equals(token)) { + args.add(token); + } + } + + for (int i = 0; i < macro.values.size(); i++) { + String value = macro.values.get(i); + + for (int j = 0; j < macro.params.size(); j++) { + String param = macro.params.get(j); + if(param.equals(value)) { + value = args.get(j); + break; + } + } + + if(isIdentifier(value)) { + newS +=" "; + } + + newS += value; + + } + + } + + print(newS); + } + } + flush(); + } + + private void preprocessorDirective() throws IOException { + String w = nextWord(); + boolean shouldPrint = true; + if (w.equals("warning")) { + handleWarning(); + shouldPrint = false; + } else if (w.equals("error")) { + handleError(); + shouldPrint = false; + } else if (w.equals("define")) { + handleDefine(); + shouldPrint = false; + } else if (w.equals("undef")) { + handleUndefine(); + shouldPrint = false; + } else if (w.equals("if") || w.equals("elif")) { + handleIf(w.equals("if")); + shouldPrint = false; + } else if (w.equals("ifdef") || w.equals("ifndef")) { + handleIfdef(w.equals("ifdef")); + shouldPrint = false; + } else if (w.equals("else")) { + handleElse(); + shouldPrint = false; + } else if (w.equals("endif")) { + handleEndif(); + shouldPrint = false; + } else if (w.equals("include")) { + handleInclude(); + shouldPrint = false; + } else { + // Unknown preprocessor directive (#pragma?) -- ignore + } + if (shouldPrint) { + print("# "); + printToken(); + } + } + + //////////////////////////////////// + // Handling of #define directives // + //////////////////////////////////// + + private void handleUndefine() throws IOException { + // Next token is the name of the #undef + String name = nextWord(); + + debugPrint(true, "#undef " + name); + + // there shouldn't be any extra symbols after the name, but just in case... + List<String> values = new ArrayList<String>(); + while (nextToken(true) != StreamTokenizer.TT_EOL) { + values.add(curTokenAsString()); + } + + if (enabled()) { + String oldDef = defineMap.remove(name); + if (oldDef == null) { + LOG.log(WARNING, "ignoring redundant \"#undef {0}\", at \"{1}\" line {2}: \"{3}\" was not previously defined", + new Object[]{name, filename(), lineNumber(), name}); + } else { + // System.err.println("UNDEFINED: '" + name + "' (line " + lineNumber() + " file " + filename() + ")"); + } + nonConstantDefines.remove(name); + } else { + LOG.log(WARNING, "FAILED TO UNDEFINE: ''{0}'' (line {1} file {2})", new Object[]{name, lineNumber(), filename()}); + } + } + + private void handleWarning() throws IOException { + String msg = nextWordOrString(); + if (enabled()) { + LOG.log(WARNING, "#warning {0} at \"{1}\" line \"{2}\"", new Object[]{msg, filename(), lineNumber()}); + } + } + + private void handleError() throws IOException { + String msg = nextWordOrString(); + if (enabled()) { + LOG.log(WARNING, "#error {0} at \"{1}\" line \"{2}\"", new Object[]{msg, filename(), lineNumber()}); + } + } + + private void handleDefine() throws IOException { + + // (workaround for not having a lookahead) + // macro functions have no space between identifier and '(' + // since whitespace is our delimiter we can't determine wether we are dealing with + // macros or normal defines starting with a brace. + // this will glue the brace to the token if there is no whitespace between both + state.tok.wordChars('(', '('); + + // Next token is the name of the #define + String name = nextWord(); + + boolean macroDefinition = name.contains("("); + + //System.err.println("IN HANDLE_DEFINE: '" + name + "' (line " + lineNumber() + " file " + filename() + ")"); + // (Note that this is not actually proper handling for multi-line #defines) + List<String> values = new ArrayList<String>(); + + if(macroDefinition) { + int index = name.indexOf('('); + String var = name.substring(index+1); + name = name.substring(0, index); + + values.add("("); + values.add(var); + } + + // restore normal syntax + state.tok.ordinaryChar('('); + + while (nextToken(true) != StreamTokenizer.TT_EOL) { + values.add(curTokenAsString()); + } + // if we're not within an active block of code (like inside an "#ifdef + // FOO" where FOO isn't defined), then don't actually alter the definition + // map. + debugPrint(true, "#define " + name); + if (enabled()) { + boolean emitDefine = true; + + // Handle #definitions to nothing or to a constant value + int sz = values.size(); + if (sz == 0) { + // definition to nothing, like "#define FOO" + String value = ""; + String oldDef = defineMap.put(name, value); + if (oldDef != null && !oldDef.equals(value)) { + LOG.log(WARNING, "\"{0}\" redefined from \"{1}\" to \"\"", new Object[]{name, oldDef}); + } + // We don't want to emit the define, because it would serve no purpose + // and cause GlueGen errors (confuse the GnuCParser) + emitDefine = false; + //System.out.println("//---DEFINED: " + name + "to \"\""); + } else if (sz == 1) { + // See whether the value is a constant + String value = values.get(0); + + if (isConstant(value)) { + // Value is numeric constant like "#define FOO 5". + // Put it in the #define map + String oldDef = defineMap.put(name, value); + if (oldDef != null && !oldDef.equals(value)) { + LOG.log(WARNING, "\"{0}\" redefined from \"{1}\" to \"{2}\"", new Object[]{name, oldDef, value}); + } + debugPrint(true, "#define " + name + " ["+oldDef+" ] -> "+value + " CONST"); + //System.out.println("//---DEFINED: " + name + " to \"" + value + "\""); + } else { + debugPrint(true, "#define " + name + " -> "+value + " SYMB"); + // Value is a symbolic constant like "#define FOO BAR". + // Try to look up the symbol's value + String newValue = resolveDefine(value, true); + if (newValue != null) { + // Set the value to the value of the symbol. + // + // TO DO: Is this correct? Why not output the symbol unchanged? + // I think that it's a good thing to see that some symbols are + // defined in terms of others. -chris + values.set(0, newValue); + } else { + // Still perform textual replacement + defineMap.put(name, value); + nonConstantDefines.add(name); + emitDefine = false; + } + } + + } else if (macroDefinition) { + + // list parameters + List<String> params = new ArrayList<String>(); + for (int i = 1; i < values.size(); i++) { + String v = values.get(i); + if(")".equals(v)) { // end of params + if(i != values.size()-1) { + values = values.subList(i+1, values.size()); + }else{ + values = Collections.emptyList(); + } + break; + }else if(!",".equals(v)) { + params.add(v); + } + } + + Macro macro = new Macro(params, values); + Macro oldDef = macroMap.put(name, macro); + if (oldDef != null) { + LOG.log(WARNING, "\"{0}\" redefined from \"{1}\" to \"{2}\"", new Object[]{name, oldDef, macro}); + } + emitDefine = false; + + }else{ + + // find constant expressions like (1 << 3) + // if found just pass them through, they will most likely work in java too + // expressions containing identifiers are currently ignored (casts too) + + boolean containsIdentifier = false; + for (String value : values) { + if(isIdentifier(value)) { + containsIdentifier = true; + break; + } + } + + //TODO more work here e.g casts are currently not handled + if(containsIdentifier) { //skip + + // Non-constant define; try to do reasonable textual substitution anyway + // (FIXME: should identify some of these, like (-1), as constants) + emitDefine = false; + StringBuilder val = new StringBuilder(); + for (int i = 0; i < sz; i++) { + if (i != 0) { + val.append(" "); + } + val.append(resolveDefine(values.get(i), false)); + } + if (defineMap.get(name) != null) { + // This is probably something the user should investigate. + throw new RuntimeException("Cannot redefine symbol \"" + name + + " from \"" + defineMap.get(name) + "\" to non-constant " + + " definition \"" + val.toString() + "\""); + } + defineMap.put(name, val.toString()); + nonConstantDefines.add(name); + + }else{ // constant expression -> pass through + + StringBuilder sb = new StringBuilder(); + for (String v : values) { + sb.append(v); + } + String value = sb.toString(); + + String oldDef = defineMap.put(name, value); + if (oldDef != null && !oldDef.equals(value)) { + LOG.log(WARNING, "\"{0}\" redefined from \"{1}\" to \"{2}\"", new Object[]{name, oldDef, value}); + } + debugPrint(true, "#define " + name + " ["+oldDef+" ] -> "+value + " CONST"); +// System.out.println("#define " + name +" "+value + " CONST EXPRESSION"); + } + + } + + if (emitDefine) { + // Print name and value + print("# define "); + print(name); + print(" "); + for (String v : values) { + print(v); + } + println(); + } + + } // end if (enabled()) + + //System.err.println("OUT HANDLE_DEFINE: " + name); + } + + private boolean isIdentifier(String value) { + + boolean identifier = false; + + char[] chars = value.toCharArray(); + + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (i == 0) { + if (Character.isJavaIdentifierStart(c)) { + identifier = true; + } + } else { + if (!Character.isJavaIdentifierPart(c)) { + identifier = false; + break; + } + } + } + return identifier; + } + + private boolean isConstant(String s) { + if (s.startsWith("0x") || s.startsWith("0X")) { + return checkHex(s); + } else { + return checkDecimal(s); + } + } + + private boolean checkHex(String s) { + char c='\0'; + int i; + for (i = 2; i < s.length(); i++) { + c = s.charAt(i); + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + break; + } + } + if(i==s.length()) { + return true; + } else if(i==s.length()-1) { + // Const qualifier .. + return c == 'l' || c == 'L' || + c == 'f' || c == 'F' || + c == 'u' || c == 'U' ; + } + return false; + } + + private boolean checkDecimal(String s) { + try { + Float.valueOf(s); + } catch (NumberFormatException e) { + // not parsable as a number + return false; + } + return true; + } + + private String resolveDefine(String word, boolean returnNullIfNotFound) { + String lastWord = defineMap.get(word); + if (lastWord == null) { + if (returnNullIfNotFound) { + return null; + } + return word; + } + String nextWord = null; + do { + nextWord = defineMap.get(lastWord); + if (nextWord != null) { + lastWord = nextWord; + } + } while (nextWord != null); + return lastWord; + } + + //////////////////////////////////////////////// + // Handling of #if/#ifdef/ifndef/endif directives // + //////////////////////////////////////////////// + + /** + * @param isIfdef if true, we're processing #ifdef; if false, we're + * processing #ifndef. + */ + private void handleIfdef(boolean isIfdef) throws IOException { + // Next token is the name of the #ifdef + String symbolName = nextWord(); + debugPrint(true, (isIfdef ? "#ifdef " : "#ifndef ") + symbolName); + boolean symbolIsDefined = defineMap.get(symbolName) != null; + debugPrint(true, (isIfdef ? "#ifdef " : "#ifndef ") + symbolName + "(defined: "+symbolIsDefined+")"); + pushEnableBit(enabled() && symbolIsDefined == isIfdef); + } + + /** Handles #else directives */ + private void handleElse() throws IOException { + boolean enabledStatusBeforeElse = enabled(); + popEnableBit(); + pushEnableBit(enabled() && !enabledStatusBeforeElse); + debugPrint(true, "#else "); + } + + private void handleEndif() { + boolean enabledBeforePopping = enabled(); + popEnableBit(); + + // print the endif if we were enabled prior to popEnableBit() (sending + // false to debugPrint means "print regardless of current enabled() state). + debugPrint(!enabledBeforePopping, "#endif/end-else"); + } + + /** + * @param isIf if true, we're processing #if; if false, we're + * processing #elif. + */ + private void handleIf(boolean isIf) throws IOException { + //System.out.println("IN HANDLE_" + (isIf ? "IF" : "ELIF") + " file \"" + filename() + " line " + lineNumber()); + debugPrint(true, (isIf ? "#if" : "#elif")); + boolean defineEvaluatedToTrue = handleIfRecursive(true); + if (!isIf) { + popEnableBit(); + } + pushEnableBit(enabled() && defineEvaluatedToTrue); + //System.out.println("OUT HANDLE_" + (isIf ? "IF" : "ELIF") +" (evaluated to " + defineEvaluatedToTrue + ")"); + } + + //static int tmp = -1; + + /** + * This method is called recursively to process nested sub-expressions such as: + * <pre> + * #if !defined(OPENSTEP) && !(defined(NeXT) || !defined(NeXT_PDO)) + *</pre> + * + * @param greedy if true, continue evaluating sub-expressions until EOL is + * reached. If false, return as soon as the first sub-expression is + * processed. + * @return the value of the sub-expression or (if greedy==true) + * series of sub-expressions. + */ + private boolean handleIfRecursive(boolean greedy) throws IOException { + //System.out.println("IN HANDLE_IF_RECURSIVE (" + ++tmp + ", greedy = " + greedy + ")"); System.out.flush(); + + // ifValue keeps track of the current value of the potentially nested + // "defined()" expressions as we process them. + boolean ifValue = true; + int openParens = 0; + int tok; + do { + tok = nextToken(true); + //System.out.println("-- READ: [" + (tok == StreamTokenizer.TT_EOL ? "<EOL>" :curTokenAsString()) + "]"); + switch (tok) { + case '(': + ++openParens; + //System.out.println("OPEN PARENS = " + openParens); + ifValue = ifValue && handleIfRecursive(true); + break; + case ')': + --openParens; + //System.out.println("OPEN PARENS = " + openParens); + break; + case '!': + { + //System.out.println("HANDLE_IF_RECURSIVE HANDLING !"); + boolean rhs = handleIfRecursive(false); + ifValue = !rhs; + //System.out.println("HANDLE_IF_RECURSIVE HANDLED OUT !, RHS = " + rhs); + } + break; + case '&': + { + nextRequiredToken('&'); + //System.out.println("HANDLE_IF_RECURSIVE HANDLING &&, LHS = " + ifValue); + boolean rhs = handleIfRecursive(true); + //System.out.println("HANDLE_IF_RECURSIVE HANDLED &&, RHS = " + rhs); + ifValue = ifValue && rhs; + } + break; + case '|': + { + nextRequiredToken('|'); + //System.out.println("HANDLE_IF_RECURSIVE HANDLING ||, LHS = " + ifValue); + boolean rhs = handleIfRecursive(true); + //System.out.println("HANDLE_IF_RECURSIVE HANDLED ||, RHS = " + rhs); + ifValue = ifValue || rhs; + } + break; + case '>': + { + // NOTE: we don't handle expressions like this properly + boolean rhs = handleIfRecursive(true); + ifValue = false; + } + break; + case '<': + { + // NOTE: we don't handle expressions like this properly + boolean rhs = handleIfRecursive(true); + ifValue = false; + } + break; + case '=': + { + // NOTE: we don't handle expressions like this properly + boolean rhs = handleIfRecursive(true); + ifValue = false; + } + break; + case StreamTokenizer.TT_WORD: + { + String word = curTokenAsString(); + if (word.equals("defined")) { + // Handle things like #if defined(SOMESYMBOL) + nextRequiredToken('('); + String symbol = nextWord(); + boolean isDefined = defineMap.get(symbol) != null; + //System.out.println("HANDLE_IF_RECURSIVE HANDLING defined(" + symbol + ") = " + isDefined); + ifValue = ifValue && isDefined; + nextRequiredToken(')'); + } else { + // Handle things like #if SOME_SYMBOL. + String symbolValue = defineMap.get(word); + + // See if the statement is "true"; i.e., a non-zero expression + if (symbolValue != null) { + // The statement is true if the symbol is defined and is a constant expression + return (!nonConstantDefines.contains(word)); + } else { + // The statement is true if the symbol evaluates to a non-zero value + // + // NOTE: This doesn't yet handle evaluable expressions like "#if + // SOME_SYMBOL > 5" or "#if SOME_SYMBOL == 0", both of which are + // valid syntax. It only handles numeric symbols like "#if 1" + + try { + // see if it's in decimal form + return Double.parseDouble(word) != 0; + } catch (NumberFormatException nfe1) { + try { + // ok, it's not a valid decimal value, try hex/octal value + return Long.parseLong(word) != 0; + } catch (NumberFormatException nfe2) { + try { + // ok, it's not a valid hex/octal value, try boolean + return Boolean.valueOf(word) == Boolean.TRUE; + } catch (NumberFormatException nfe3) { + // give up; the symbol isn't a numeric or boolean value + return false; + } + } + } + } + } + } // end case TT_WORD + break; + case StreamTokenizer.TT_EOL: + //System.out.println("HANDLE_IF_RECURSIVE HIT <EOL>!"); + state.pushBackToken(); // so caller hits EOL as well if we're recursing + break; + case StreamTokenizer.TT_EOF: + throw new RuntimeException("Unexpected end of file while parsing " + + "#if statement at file " + filename() + ", line " + lineNumber()); + + default: + throw new RuntimeException("Unexpected token (" + curTokenAsString() + + ") while parsing " + "#if statement at file " + filename() + + ", line " + lineNumber()); + } + //System.out.println("END OF WHILE: greedy = " + greedy + " parens = " +openParens + " not EOL = " + (tok != StreamTokenizer.TT_EOL) + " --> " + ((greedy && openParens >= 0) && tok != StreamTokenizer.TT_EOL)); + } while ((greedy && openParens >= 0) && tok != StreamTokenizer.TT_EOL); + //System.out.println("OUT HANDLE_IF_RECURSIVE (" + tmp-- + ", returning " + ifValue + ")"); + //System.out.flush(); + return ifValue; + } + + ///////////////////////////////////// + // Handling of #include directives // + ///////////////////////////////////// + + private void handleInclude() throws IOException { + // Two kinds of #includes: one with quoted string for argument, + // one with angle brackets surrounding argument + int t = nextToken(); + String filename = null; + if (t == '"') { + filename = state.curWord(); + } else if (t == '<') { + // Components of path name are coming in as separate tokens; + // concatenate them + StringBuilder buf = new StringBuilder(); + while ((t = nextToken()) != '>' && (t != StreamTokenizer.TT_EOF)) { + buf.append(curTokenAsString()); + } + if (t == StreamTokenizer.TT_EOF) { + LOG.warning("unexpected EOF while processing #include directive"); + } + filename = buf.toString(); + } + // if we're not within an active block of code (like inside an "#ifdef + // FOO" where FOO isn't defined), then don't actually process the + // #included file. + debugPrint(true, "#include [" + filename + "]"); + if (enabled()) { + // Look up file in known #include path + String fullname = findFile(filename); + //System.out.println("ACTIVE BLOCK, LOADING " + filename); + if (fullname == null) { + LOG.log(WARNING, "unable to find #include file \"{0}\"", filename); + return; + } + // Process this file in-line + Reader reader = new BufferedReader(new FileReader(fullname)); + run(reader, fullname); + } else { + //System.out.println("INACTIVE BLOCK, SKIPPING " + filename); + } + } + + //////////// + // Output // + //////////// + + private OutputStream out; + private PrintWriter writer; + private List<Boolean> enabledBits = new ArrayList<Boolean>(); + + private static int debugPrintIndentLevel = 0; + + private void debugPrint(boolean onlyPrintIfEnabled, String msg) { + if (!enableDebugPrint) { + return; + } + + if (!onlyPrintIfEnabled || (onlyPrintIfEnabled && enabled())) { + for (int i = debugPrintIndentLevel; --i > 0;) { + System.out.print(" "); + } + System.out.println(msg + " (line " + lineNumber() + " file " + filename() + ")"); + } + } + + private void pushEnableBit(boolean enabled) { + enabledBits.add(enabled); + ++debugPrintIndentLevel; + debugPrint(false, "PUSH_ENABLED, NOW: " + enabled()); + } + + private void popEnableBit() { + if (enabledBits.isEmpty()) { + LOG.warning("mismatched #ifdef/endif pairs"); + return; + } + enabledBits.remove(enabledBits.size() - 1); + --debugPrintIndentLevel; + debugPrint(false, "POP_ENABLED, NOW: " + enabled()); + } + + private boolean enabled() { + return (enabledBits.isEmpty() || enabledBits.get(enabledBits.size() - 1)); + } + + private void print(String s) { + if (enabled()) { + writer.print(s); + //System.out.print(s);//debug + } + } + + private void print(char c) { + if (enabled()) { + writer.print(c); + //System.err.print(c); //debug + } + } + + private void println() { + if (enabled()) { + writer.println(); + //System.err.println();//debug + } + } + + private void printToken() { + print(curTokenAsString()); + } + + private void flush() { + if (enabled()) { + writer.flush(); + //System.err.flush(); //debug + } + } + + private void lineDirective() { + print("# " + lineNumber() + " \"" + filename() + "\""); + println(); + } + + private static void usage() { + System.out.println("Usage: java PCPP [filename | -]"); + System.out.println("Minimal pseudo-C-preprocessor."); + System.out.println("Output goes to standard output. Standard input can be used as input"); + System.out.println("by passing '-' as the argument."); + System.out.println(" --debug enables debug mode"); + System.exit(1); + } + + public static void main(String[] args) throws IOException { + Reader reader = null; + String filename = null; + boolean debug = false; + + if (args.length == 0) { + usage(); + } + + List<String> includePaths = new ArrayList<String>(); + for (int i = 0; i < args.length; i++) { + if (i < args.length - 1) { + String arg = args[i]; + if (arg.startsWith("-I")) { + String[] paths = arg.substring(2).split(System.getProperty("path.separator")); + for (int j = 0; j < paths.length; j++) { + includePaths.add(paths[j]); + } + } else if (arg.equals("--debug")) { + debug = true; + } else { + usage(); + } + } else { + String arg = args[i]; + if (arg.equals("-")) { + reader = new InputStreamReader(System.in); + filename = "standard input"; + } else { + if (arg.startsWith("-")) { + usage(); + } + filename = arg; + reader = new BufferedReader(new FileReader(filename)); + } + } + } + + new PCPP(includePaths, debug).run(reader, filename); + } + +} |