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