001    /* JAPI - (Yet another (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:chris@riedquat.de">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         * @throws IllegalArgumentException in case <var>file</var> is not a directory
077         */
078        public void addIncludePath(final File file) {
079            if (!file.isDirectory()) {
080                throw new IllegalArgumentException("Only directories are valid for include paths.");
081            }
082            includePaths.add(file);
083        }
084    
085        /** Get a list with all include paths.
086         * @return list with all include paths
087         */
088        public List<File> getIncludePaths() {
089            return unmodifiableList(includePaths);
090        }
091    
092        private String include(final String filename) throws IOException {
093            synchronized (syncLock) {
094                if (!includes.containsKey(filename)) {
095                    includes.put(filename, readFile(filename));
096                }
097                assert includes.get(filename) != null;
098                return includes.get(filename);
099            }
100        }
101    
102        private static String readFile(final String filename) throws IOException {
103            final StringBuilder chain = new StringBuilder();
104            final char[] buffer = new char[4096];
105            Reader in = null;
106            try {
107                in = new FileReader(filename);
108                for (int bytesRead; (bytesRead = in.read(buffer)) != -1;) {
109                    chain.append(buffer, 0, bytesRead);
110                }
111            } finally {
112                try { in.close(); } catch (final Exception e) { /* ignore */ }
113            }
114            return chain.toString();
115        }
116    
117        public void process(final Reader in, final Writer out) throws IOException {
118            new Processor(in, out);
119        }
120    
121        private class Processor {
122    
123            private final Reader in;
124            private final Writer out;
125            private int c;
126            private StringBuilder currentDirective = new StringBuilder();
127            private StringBuilder currentDirectiveArgs = new StringBuilder();
128    
129            private Processor(final Reader inStream, final Writer outStream) throws IOException {
130                in  = inStream instanceof BufferedReader ? (BufferedReader) inStream : new BufferedReader(inStream);
131                out = outStream instanceof PrintWriter ? (PrintWriter) outStream : new PrintWriter(outStream);
132                run();
133                out.flush();
134            }
135    
136            private void run() throws IOException {
137                State state = State.NORMAL;
138                while ((c = in.read()) != -1) {
139                    switch (state) {
140                        case NORMAL:
141                            switch (c) {
142                                case '#':  state = DIRECTIVE;     break;
143                                case '/':  state = MAYBE_COMMENT; break;
144                                case '"':  out.write(c); state = STRING; break;
145                                case '\'': out.write(c); state = CHAR; break;
146                                default: out.write(c);
147                            } break;
148                        case DIRECTIVE:
149                            switch (c) {
150                                case '\\': state = DIRECTIVE_ESCAPE; break;
151                                case '\n': state = NORMAL; processDirective();
152                            } break;
153                        case DIRECTIVE_ESCAPE:
154                            state = DIRECTIVE; break;
155                        case MAYBE_COMMENT:
156                            switch (c) {
157                                case '*': if (!stripComments) { out.write('/'); out.write(c); } state = MULTILINE_COMMENT; break;
158                                case '/': if (!stripComments) { out.write('/'); out.write(c); } state = EOL_COMMENT; break;
159                                default: out.write('/'); out.write(c);
160                            } break;
161                        case MULTILINE_COMMENT:
162                            switch (c) {
163                                case '*': if (!stripComments) { out.write(c); } state = MAYBE_ENDOF_COMMENT; break;
164                                default: if (!stripComments) { out.write(c); }
165                            } break;
166                        case EOL_COMMENT:
167                            switch (c) {
168                                case '\n': out.write(c); state = NORMAL; break;
169                                default: if (!stripComments) { out.write(c); }
170                            } break;
171                        case MAYBE_ENDOF_COMMENT:
172                            switch (c) {
173                                case '/': if (!stripComments) { out.write(c); } state = NORMAL; break;
174                                case '*': if (!stripComments) { out.write(c); } break;
175                                default: if (!stripComments) { out.write(c); } state = MULTILINE_COMMENT; break;
176                            } break;
177                        case STRING:
178                            switch (c) {
179                                case '\\': state = STRING_ESCAPE; break;
180                                case '"': state = NORMAL; break;
181                            } break;
182                        case STRING_ESCAPE:
183                            state = STRING; break;
184                        case CHAR:
185                            switch (c) {
186                                case '\\': state = CHAR_ESCAPE; break;
187                                case '\'': state = NORMAL; break;
188                            } break;
189                        case CHAR_ESCAPE:
190                            state = CHAR; break;
191                    }
192                }
193            }
194    
195            private void processDirective() {
196            }
197    
198        } // class Processor
199    
200        /** Main program.
201         * @param args command line arguments
202         */
203        public static void main(final String... args) throws IOException {
204            new CPreProcessor().process(System.in, System.out);
205        }
206    
207        public void process(final InputStream in, final OutputStream out) throws IOException {
208            process(new InputStreamReader(in), new OutputStreamWriter(out));
209        }
210    
211    } // class CPreProcessor