com.jogamp.gluegen.pcpp.PCPP Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gluegen-rt Show documentation
Show all versions of gluegen-rt Show documentation
JNI binding generator (runtime)
/*
* 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 final Map defineMap = new HashMap(128);
private final Map macroMap = new HashMap(128);
private final Set nonConstantDefines = new HashSet(128);
/** List containing the #include paths as Strings */
private final List includePaths;
private ParseState state;
private final boolean enableDebugPrint;
private final boolean enableCopyOutput2Stderr;
public PCPP(final List includePaths, final boolean debug, final boolean copyOutput2Stderr) {
this.includePaths = includePaths;
setOut(System.out);
enableDebugPrint = debug;
enableCopyOutput2Stderr = copyOutput2Stderr;
}
public void run(final Reader reader, final 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);
final ParseState curState = new ParseState(tok, filename);
final ParseState oldState = state;
state = curState;
lineDirective();
parse();
state = oldState;
if (state != null) {
lineDirective();
}
}
private void initTokenizer(final 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(final String filename) {
final String sep = File.separator;
for (final String inclPath : includePaths) {
final String fullPath = inclPath + sep + filename;
final File file = new File(fullPath);
if (file.exists()) {
return fullPath;
}
}
return null;
}
public OutputStream out() {
return out;
}
public void setOut(final OutputStream out) {
this.out = out;
writer = new PrintWriter(out);
}
// State
static class ParseState {
private final StreamTokenizer tok;
private final String filename;
private boolean startOfLine;
private boolean startOfFile;
ParseState(final StreamTokenizer tok, final 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(final boolean val) {
startOfLine = val;
}
boolean startOfFile() {
return startOfFile;
}
void setStartOfFile(final boolean val) {
startOfFile = val;
}
}
private static class Macro {
private final List values;
private final List params;
Macro(final List params, final List 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(final boolean returnEOLs) throws IOException {
final 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(final int requiredToken) throws IOException {
final int nextTok = nextToken();
if (nextTok != requiredToken) {
String msg = "Expected token '" + requiredToken + "' but got ";
switch (nextTok) {
case StreamTokenizer.TT_EOF: msg += ""; break;
case StreamTokenizer.TT_EOL: msg += ""; break;
default: msg += "'" + curTokenAsString() + "'"; break;
}
msg += " at file " + filename() + ", line " + lineNumber();
throw new IOException(msg);
}
}
private String curTokenAsString() {
final 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 at file " + filename() + ", line " + lineNumber());
}
final char c = (char) t;
if (c == '"' || c == '\'') {
final 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 {
final 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 (null != state) ? state.filename() : null;
}
private int lineNumber() {
return (null != state) ? state.lineNumber() : -1;
}
/////////////
// 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(" ");
}
final String s = curTokenAsString();
String newS = defineMap.get(s);
if (newS == null) {
newS = s;
}
final Macro macro = macroMap.get(newS);
if(macro != null) {
newS = "";
final List args = new ArrayList();
while (nextToken() != StreamTokenizer.TT_EOL) {
final 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++) {
final 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 {
final 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 {
int line = -1;
try {
// try '# ""' case
line = Integer.parseInt(w);
final String filename = nextWordOrString();
print("# " + line + " " + filename);
println();
shouldPrint = false;
} catch (final NumberFormatException nfe) {
// 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
final String name = nextWord();
debugPrint(true, "UNDEF " + name);
// there shouldn't be any extra symbols after the name, but just in case...
final List values = new ArrayList();
while (nextToken(true) != StreamTokenizer.TT_EOL) {
values.add(curTokenAsString());
}
if (enabled()) {
final 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 {
final String msg = nextWordOrString();
if (enabled()) {
LOG.log(WARNING, "#warning {0} at \"{1}\" line \"{2}\"", new Object[]{msg, filename(), lineNumber()});
}
}
private void handleError() throws IOException {
final String msg = nextWordOrString();
if (enabled()) {
throw new RuntimeException("#error "+msg+" at \""+filename()+"\" line "+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();
final 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)
final List values = new ArrayList();
if(macroDefinition) {
final int index = name.indexOf('(');
final 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());
}
addDefine(name, macroDefinition, values);
}
public void addDefine(final String name, final String value) {
final List values = new ArrayList();
values.add(value);
addDefine(name, false, values);
}
private void addDefine(final String name, final boolean nameIsMacro, List values) {
// 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
final int sz = values.size();
if (sz == 0) {
// definition to nothing, like "#define FOO"
final String value = "";
final 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.err.println("//---DEFINED: " + name + "to \"\"");
} else if (sz == 1) {
// See whether the value is a constant
final String value = values.get(0);
if (isConstant(value)) {
// Value is numeric constant like "#define FOO 5".
// Put it in the #define map
final 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.err.println("//---DEFINED: " + name + " to \"" + value + "\"");
} else {
// Value is a symbolic constant like "#define FOO BAR".
// Try to look up the symbol's value
final String newValue = resolveDefine(value, true);
debugPrint(true, "DEFINE " + name + " -> "+value + " -> <" + newValue + "> SYMB");
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
final boolean valueIsMacro = newValue.contains("(");
if(valueIsMacro) {
// parser can't dig this currently
emitDefine = false;
} else {
values.set(0, newValue);
}
} else {
// Still perform textual replacement
defineMap.put(name, value);
nonConstantDefines.add(name);
emitDefine = false;
}
}
} else if (nameIsMacro) {
// list parameters
final List params = new ArrayList();
for (int i = 1; i < values.size(); i++) {
final 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);
}
}
final Macro macro = new Macro(params, values);
final 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 (final 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;
final 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() + "\"" +
" at file \"" + filename() + ", line " + lineNumber() );
}
defineMap.put(name, val.toString());
nonConstantDefines.add(name);
}else{ // constant expression -> pass through
final StringBuilder sb = new StringBuilder();
for (final String v : values) {
sb.append(v);
}
final String value = sb.toString();
final 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.err.println("#define " + name +" "+value + " CONST EXPRESSION");
}
}
if (emitDefine) {
// Print name and value
print("# define ");
print(name);
print(" ");
for (final String v : values) {
print(v);
}
println();
}
} // end if (enabled())
//System.err.println("OUT HANDLE_DEFINE: " + name);
}
private boolean isIdentifier(final String value) {
boolean identifier = false;
final char[] chars = value.toCharArray();
for (int i = 0; i < chars.length; i++) {
final 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(final String s) {
if (s.startsWith("0x") || s.startsWith("0X")) {
return checkHex(s);
} else {
return checkDecimal(s);
}
}
private boolean checkHex(final 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(final String s) {
try {
Float.valueOf(s);
} catch (final NumberFormatException e) {
// not parsable as a number
return false;
}
return true;
}
private String resolveDefine(final String word, final 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
*
* condition - the actual if-elif condition
* whole-block - the whole if-else-endif block
* inside-block - the inner block between if-elif-else-endif
*
* Outside - reflects the state at entering the whole-block
* Condition - reflects the state of the condition
* Inside - reflects the state within the inside-block
*/
/**
* @param isIfdef if true, we're processing #ifdef; if false, we're
* processing #ifndef.
*/
private void handleIfdef(final boolean isIfdef) throws IOException {
// Next token is the name of the #ifdef
final String symbolName = nextWord();
final boolean enabledOutside = enabled();
final boolean symbolIsDefined = defineMap.get(symbolName) != null;
debugPrint(false, (isIfdef ? "IFDEF " : "IFNDEF ") + symbolName + ", enabledOutside " + enabledOutside + ", isDefined " + symbolIsDefined + ", file \"" + filename() + " line " + lineNumber());
final boolean enabledNow = enabled() && symbolIsDefined == isIfdef ;
pushEnableBit( enabledNow ) ; // StateCondition
pushEnableBit( enabledNow ) ; // StateInside
}
/** Handles #else directives */
private void handleElse() throws IOException {
popEnableBit(); // Inside
final boolean enabledCondition = enabled();
popEnableBit(); // Condition
final boolean enabledOutside = enabled();
debugPrint(false, "ELSE, enabledOutside " + enabledOutside + ", file \"" + filename() + " line " + lineNumber());
pushEnableBit(enabledOutside && !enabledCondition); // Condition - don't care
pushEnableBit(enabledOutside && !enabledCondition); // Inside
}
private void handleEndif() {
popEnableBit(); // Inside
popEnableBit(); // Condition
final boolean enabledOutside = enabled();
// print the endif if we were enabled prior to popEnableBit() (sending
// false to debugPrint means "print regardless of current enabled() state).
debugPrint(false, "ENDIF, enabledOutside " + enabledOutside);
}
/**
* @param isIf if true, we're processing #if; if false, we're
* processing #elif.
*/
private void handleIf(final boolean isIf) throws IOException {
boolean enabledCondition = false;
boolean enabledOutside;
if (!isIf) {
popEnableBit(); // Inside
enabledCondition = enabled();
popEnableBit(); // Condition
}
enabledOutside = enabled();
final boolean defineEvaluatedToTrue = handleIfRecursive(true);
debugPrint(false, (isIf ? "IF" : "ELIF") + ", enabledOutside " + enabledOutside + ", eval " + defineEvaluatedToTrue + ", file \"" + filename() + " line " + lineNumber());
boolean enabledNow;
if(isIf) {
enabledNow = enabledOutside && defineEvaluatedToTrue ;
pushEnableBit( enabledNow ) ; // Condition
pushEnableBit( enabledNow ) ; // Inside
} else {
enabledNow = enabledOutside && !enabledCondition && defineEvaluatedToTrue ;
pushEnableBit( enabledCondition || enabledNow ) ; // Condition
pushEnableBit( enabledNow ) ; // Inside
}
}
//static int tmp = -1;
/**
* This method is called recursively to process nested sub-expressions such as:
*
* #if !defined(OPENSTEP) && !(defined(NeXT) || !defined(NeXT_PDO))
*
*
* @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(final boolean greedy) throws IOException {
//System.err.println("IN HANDLE_IF_RECURSIVE (" + ++tmp + ", greedy = " + greedy + ")"); System.err.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.err.println("-- READ: [" + (tok == StreamTokenizer.TT_EOL ? "" :curTokenAsString()) + "]");
switch (tok) {
case '(':
++openParens;
//System.err.println("OPEN PARENS = " + openParens);
ifValue = ifValue && handleIfRecursive(true);
break;
case ')':
--openParens;
//System.err.println("OPEN PARENS = " + openParens);
break;
case '!':
{
//System.err.println("HANDLE_IF_RECURSIVE HANDLING !");
final boolean rhs = handleIfRecursive(false);
ifValue = !rhs;
//System.err.println("HANDLE_IF_RECURSIVE HANDLED OUT !, RHS = " + rhs);
}
break;
case '&':
{
nextRequiredToken('&');
//System.err.println("HANDLE_IF_RECURSIVE HANDLING &&, LHS = " + ifValue);
final boolean rhs = handleIfRecursive(true);
//System.err.println("HANDLE_IF_RECURSIVE HANDLED &&, RHS = " + rhs);
ifValue = ifValue && rhs;
}
break;
case '|':
{
nextRequiredToken('|');
//System.err.println("HANDLE_IF_RECURSIVE HANDLING ||, LHS = " + ifValue);
final boolean rhs = handleIfRecursive(true);
//System.err.println("HANDLE_IF_RECURSIVE HANDLED ||, RHS = " + rhs);
ifValue = ifValue || rhs;
}
break;
case '>':
{
// NOTE: we don't handle expressions like this properly
final boolean rhs = handleIfRecursive(true);
ifValue = false;
}
break;
case '<':
{
// NOTE: we don't handle expressions like this properly
final boolean rhs = handleIfRecursive(true);
ifValue = false;
}
break;
case '=':
{
// NOTE: we don't handle expressions like this properly
final boolean rhs = handleIfRecursive(true);
ifValue = false;
}
break;
case StreamTokenizer.TT_WORD:
{
final String word = curTokenAsString();
if (word.equals("defined")) {
// Handle things like #if defined(SOMESYMBOL)
nextRequiredToken('(');
final String symbol = nextWord();
final boolean isDefined = defineMap.get(symbol) != null;
//System.err.println("HANDLE_IF_RECURSIVE HANDLING defined(" + symbol + ") = " + isDefined);
ifValue = ifValue && isDefined;
nextRequiredToken(')');
} else {
// Handle things like #if SOME_SYMBOL.
final 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 (final NumberFormatException nfe1) {
try {
// ok, it's not a valid decimal value, try hex/octal value
return Long.parseLong(word) != 0;
} catch (final NumberFormatException nfe2) {
// ok, it's not a valid hex/octal value, try boolean last
return Boolean.valueOf(word).booleanValue();
}
}
}
}
} // end case TT_WORD
break;
case StreamTokenizer.TT_EOL:
//System.err.println("HANDLE_IF_RECURSIVE HIT !");
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.err.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.err.println("OUT HANDLE_IF_RECURSIVE (" + tmp-- + ", returning " + ifValue + ")");
//System.err.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
final 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
final String fullname = findFile(filename);
//System.err.println("ACTIVE BLOCK, LOADING " + filename);
if (fullname == null) {
throw new RuntimeException("Can't find #include file \"" + filename + "\" at file " + filename() + ", line " + lineNumber());
}
// Process this file in-line
final Reader reader = new BufferedReader(new FileReader(fullname));
run(reader, fullname);
} else {
//System.err.println("INACTIVE BLOCK, SKIPPING " + filename);
}
}
////////////
// Output //
////////////
private OutputStream out;
private PrintWriter writer;
private final List enabledBits = new ArrayList();
private static int debugPrintIndentLevel = 0;
private void debugPrint(final boolean onlyPrintIfEnabled, final String msg) {
if (!enableDebugPrint) {
return;
}
if (!onlyPrintIfEnabled || (onlyPrintIfEnabled && enabled())) {
for (int i = debugPrintIndentLevel; --i > 0;) {
System.err.print(" ");
}
System.err.println("STATE: " + msg + " (line " + lineNumber() + " file " + filename() + ")");
System.err.flush();
}
}
private void pushEnableBit(final boolean enabled) {
enabledBits.add(enabled);
++debugPrintIndentLevel;
debugPrint(false, "PUSH_ENABLED, NOW: " + enabled());
}
private void popEnableBit() {
if (enabledBits.isEmpty()) {
throw new RuntimeException("mismatched #ifdef/endif pairs at file " + filename() + ", line " + lineNumber());
}
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(final String s) {
if (enabled()) {
writer.print(s);
if (enableCopyOutput2Stderr) {
System.err.print(s);
System.err.flush();
return;
}
}
}
private void print(final char c) {
if (enabled()) {
writer.print(c);
if (enableCopyOutput2Stderr) {
System.err.print(c);
System.err.flush();
return;
}
}
}
private void println() {
if (enabled()) {
writer.println();
if (enableCopyOutput2Stderr) {
System.err.println();
System.err.flush();
return;
}
}
}
private void printToken() {
print(curTokenAsString());
}
private void flush() {
if (enabled()) {
writer.flush();
if (enableCopyOutput2Stderr) {
System.err.flush();
return;
}
}
}
private void lineDirective() {
print("# " + lineNumber() + " \"" + filename() + "\"");
println();
}
private static void usage() {
System.err.println("Usage: java PCPP [filename | -]");
System.err.println("Minimal pseudo-C-preprocessor.");
System.err.println("Output goes to standard output. Standard input can be used as input");
System.err.println("by passing '-' as the argument.");
System.err.println(" --debug enables debug mode");
System.exit(1);
}
public static void main(final String[] args) throws IOException {
Reader reader = null;
String filename = null;
boolean debug = false;
if (args.length == 0) {
usage();
}
final List includePaths = new ArrayList();
for (int i = 0; i < args.length; i++) {
if (i < args.length - 1) {
final String arg = args[i];
if (arg.startsWith("-I")) {
final 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 {
final 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, debug).run(reader, filename);
}
}