All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jogamp.gluegen.pcpp.PCPP Maven / Gradle / Ivy

/*
 * 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.Level;

import com.jogamp.gluegen.ASTLocusTag;
import com.jogamp.gluegen.ConstantDefinition;
import com.jogamp.gluegen.GenericCPP;
import com.jogamp.gluegen.GlueGenException;
import com.jogamp.gluegen.Logging;
import com.jogamp.gluegen.Logging.LoggerIf;

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 implements GenericCPP {

    private final LoggerIf LOG;

    /** 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) {
        LOG = Logging.getLogger(PCPP.class.getPackage().getName(), PCPP.class.getSimpleName());
        this.includePaths = includePaths;
        setOut(System.out);
        enableDebugPrint = debug;
        enableCopyOutput2Stderr = copyOutput2Stderr;
    }

    @Override
    public void run(final Reader reader, final String filename) throws GlueGenException {
        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();
        try {
            parse();
        } catch (final Exception e) {
            final StringBuilder buf = new StringBuilder("Preprocessor failed");
            LOG.log(Level.SEVERE, buf.toString(), e);
            if( e instanceof GlueGenException ) {
                throw (GlueGenException)e;
            } else {
                throw new GlueGenException("Preprocessor failed",
                                           new ASTLocusTag(filename(), lineNumber(), -1, null), e);
            }
        }
        state = oldState;
        if (state != null) {
            lineDirective();
        }
    }

    @Override
    public List getConstantDefinitions() throws GlueGenException {
        return new ArrayList(); // NOP
    }

    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);
    }

    @Override
    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;
    }

    @Override
    public OutputStream out() {
        return out;
    }

    @Override
    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(ConstantDefinition.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, new ASTLocusTag(filename(), lineNumber(), -1, name),
                        "ignoring redundant \"#undef {0}\" - was not previously defined",
                        name);
            } else {
                // System.err.println("UNDEFINED: '" + name + "'  (line " + lineNumber() + " file " + filename() + ")");
            }
            nonConstantDefines.remove(name);
        } else {
            LOG.log(INFO, new ASTLocusTag(filename(), lineNumber(), -1, name),
                    "DISABLED UNDEFINE: ''{0}''", name);
        }
    }

    private void handleWarning() throws IOException {
        final String msg = nextWordOrString();
        if (enabled()) {
            LOG.log(WARNING, new ASTLocusTag(filename(), lineNumber(), -1, null), msg);
        }
    }

    private void handleError() throws IOException, GlueGenException {
        final String msg = nextWordOrString();
        if (enabled()) {
            throw new GlueGenException(msg, new ASTLocusTag(filename(), lineNumber(), -1, null));
        }
    }

    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);
    }

    @Override
    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, new ASTLocusTag(filename(), lineNumber(), -1, null),
                            "\"{0}\" redefined from \"{1}\" to \"\"", 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 (ConstantDefinition.isNumber(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, new ASTLocusTag(filename(), lineNumber(), -1, null),
                                "\"{0}\" redefined from \"{1}\" to \"{2}\"", 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, new ASTLocusTag(filename(), lineNumber(), -1, null),
                            "\"{0}\" redefined from \"{1}\" to \"{2}\"", 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(ConstantDefinition.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, new ASTLocusTag(filename(), lineNumber(), -1, null),
                                "\"{0}\" redefined from \"{1}\" to \"{2}\"", 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 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(false); ifValue = false; } break; case '+': { // NOTE: we don't handle expressions like this properly final boolean rhs = handleIfRecursive(false); ifValue = false; } break; case '-': { // NOTE: we don't handle expressions like this properly final boolean rhs = handleIfRecursive(false); 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(new ASTLocusTag(filename(), lineNumber(), -1, null), "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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy