001    /* JAPI - (Yet anothr (hopefully) useful) Java API
002     *
003     * Copyright (C) 2004-2006 Christian Hujer
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License as
007     * published by the Free Software Foundation; either version 2 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful, but
011     * WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018     * 02111-1307, USA.
019     */
020    
021    package net.sf.japi.cpp;
022    
023    import java.io.BufferedReader;
024    import java.io.File;
025    import java.io.FileReader;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.InputStreamReader;
029    import java.io.OutputStream;
030    import java.io.OutputStreamWriter;
031    import java.io.PrintWriter;
032    import java.io.Reader;
033    import java.io.Writer;
034    import java.util.ArrayList;
035    import static java.util.Collections.unmodifiableList;
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    import static net.sf.japi.cpp.State.CHAR;
040    import static net.sf.japi.cpp.State.CHAR_ESCAPE;
041    import static net.sf.japi.cpp.State.DIRECTIVE;
042    import static net.sf.japi.cpp.State.DIRECTIVE_ESCAPE;
043    import static net.sf.japi.cpp.State.EOL_COMMENT;
044    import static net.sf.japi.cpp.State.MAYBE_COMMENT;
045    import static net.sf.japi.cpp.State.MAYBE_ENDOF_COMMENT;
046    import static net.sf.japi.cpp.State.MULTILINE_COMMENT;
047    import static net.sf.japi.cpp.State.NORMAL;
048    import static net.sf.japi.cpp.State.STRING;
049    import static net.sf.japi.cpp.State.STRING_ESCAPE;
050    
051    /** A C Preprocessor.
052     * @author <a href="mailto:Christian.Hujer@itcqis.com">Christian Hujer</a>
053     */
054    public class CPreProcessor {
055    
056        /** Synchronization lock object. */
057        private final Object syncLock = new Object();
058    
059        /** List with include directory paths. */
060        private final List<File> includePaths = new ArrayList<File>();
061    
062        /** Map with included files. */
063        private final Map<String, String> includes = new HashMap<String, String>();
064    
065        /** Map with defined macros. */
066        private final Map<String, String> defines = new HashMap<String, String>();
067    
068        /** Setting whether to strip comments. */
069        private boolean stripComments = true;
070    
071        public CPreProcessor() {
072        }
073    
074        /** Add an include path.
075         * @param file include path to add
076         */
077        public void addIncludePath(final File file) {
078            if (!file.isDirectory()) {
079                throw new IllegalArgumentException("Only directories are valid for include paths.");
080            }
081            includePaths.add(file);
082        }
083    
084        /** Get a list with all include paths.
085         * @return list with all include paths
086         */
087        public List<File> getIncludePaths() {
088            return unmodifiableList(includePaths);
089        }
090    
091        private String include(final String filename) throws IOException {
092            synchronized (syncLock) {
093                if (!includes.containsKey(filename)) {
094                    includes.put(filename, readFile(filename));
095                }
096                assert includes.get(filename) != null;
097                return includes.get(filename);
098            }
099        }
100    
101        private static String readFile(final String filename) throws IOException {
102            final StringBuilder chain = new StringBuilder();
103            final char[] buffer = new char[4096];
104            Reader in = null;
105            try {
106                in = new FileReader(filename);
107                for (int bytesRead; (bytesRead = in.read(buffer)) != -1;) {
108                    chain.append(buffer, 0, bytesRead);
109                }
110            } finally {
111                try { in.close(); } catch (final Exception e) { /* ignore */ }
112            }
113            return chain.toString();
114        }
115    
116        public void process(final Reader in, final Writer out) throws IOException {
117            new Processor(in, out);
118        }
119    
120        private class Processor {
121    
122            private final Reader in;
123            private final Writer out;
124            private int c;
125            private StringBuilder currentDirective = new StringBuilder();
126            private StringBuilder currentDirectiveArgs = new StringBuilder();
127    
128            private Processor(final Reader inStream, final Writer outStream) throws IOException {
129                in  = inStream instanceof BufferedReader ? (BufferedReader) inStream : new BufferedReader(inStream);
130                out = outStream instanceof PrintWriter ? (PrintWriter) outStream : new PrintWriter(outStream);
131                run();
132                out.flush();
133            }
134    
135            private void run() throws IOException {
136                State state = State.NORMAL;
137                while ((c = in.read()) != -1) {
138                    switch (state) {
139                        case NORMAL:
140                            switch (c) {
141                                case '#':  state = DIRECTIVE;     break;
142                                case '/':  state = MAYBE_COMMENT; break;
143                                case '"':  out.write(c); state = STRING; break;
144                                case '\'': out.write(c); state = CHAR; break;
145                                default: out.write(c);
146                            } break;
147                        case DIRECTIVE:
148                            switch (c) {
149                                case '\\': state = DIRECTIVE_ESCAPE; break;
150                                case '\n': state = NORMAL; processDirective();
151                            } break;
152                        case DIRECTIVE_ESCAPE:
153                            state = DIRECTIVE; break;
154                        case MAYBE_COMMENT:
155                            switch (c) {
156                                case '*': if (!stripComments) { out.write('/'); out.write(c); } state = MULTILINE_COMMENT; break;
157                                case '/': if (!stripComments) { out.write('/'); out.write(c); } state = EOL_COMMENT; break;
158                                default: out.write('/'); out.write(c);
159                            } break;
160                        case MULTILINE_COMMENT:
161                            switch (c) {
162                                case '*': if (!stripComments) { out.write(c); } state = MAYBE_ENDOF_COMMENT; break;
163                                default: if (!stripComments) { out.write(c); }
164                            } break;
165                        case EOL_COMMENT:
166                            switch (c) {
167                                case '\n': out.write(c); state = NORMAL; break;
168                                default: if (!stripComments) { out.write(c); }
169                            } break;
170                        case MAYBE_ENDOF_COMMENT:
171                            switch (c) {
172                                case '/': if (!stripComments) { out.write(c); } state = NORMAL; break;
173                                case '*': if (!stripComments) { out.write(c); } break;
174                                default: if (!stripComments) { out.write(c); } state = MULTILINE_COMMENT; break;
175                            } break;
176                        case STRING:
177                            switch (c) {
178                                case '\\': state = STRING_ESCAPE; break;
179                                case '"': state = NORMAL; break;
180                            } break;
181                        case STRING_ESCAPE:
182                            state = STRING; break;
183                        case CHAR:
184                            switch (c) {
185                                case '\\': state = CHAR_ESCAPE; break;
186                                case '\'': state = NORMAL; break;
187                            } break;
188                        case CHAR_ESCAPE:
189                            state = CHAR; break;
190                    }
191                }
192            }
193    
194            private void processDirective() {
195            }
196    
197        } // class Processor
198    
199        /** Main program.
200         * @param args command line arguments
201         */
202        public static void main(final String... args) throws IOException {
203            new CPreProcessor().process(System.in, System.out);
204        }
205    
206        public void process(final InputStream in, final OutputStream out) throws IOException {
207            process(new InputStreamReader(in), new OutputStreamWriter(out));
208        }
209    
210    } // class CPreProcessor