summaryrefslogtreecommitdiffstats
path: root/src/java/com/jogamp/gluegen/pcpp/PCPP.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/jogamp/gluegen/pcpp/PCPP.java')
-rw-r--r--src/java/com/jogamp/gluegen/pcpp/PCPP.java1107
1 files changed, 1107 insertions, 0 deletions
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);
+ }
+
+}