1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
8 * A VERY crude, inefficient Java preprocessor
10 * //#define FOO bar baz -- replace all instances of token FOO with "bar baz"
11 * //#replace foo/bar baz/bop -- DUPLICATE everything between here and //#end,
12 * replacing foo with bar and baz with bop in the *second* copy
13 * //#switch(EXPR) -- switch on strings
18 * Replacements are done on a token basis. Tokens are defined as a
19 * sequence of characters which all belong to a single class. The
20 * two character classes are:
23 * - all other non-whitespace characters
25 public class Preprocessor {
27 static Hashtable replace = new Hashtable();
28 static Hashtable repeatreplace = null;
29 static Vector sinceLastRepeat = null;
31 public static void main(String[] args) throws IOException {
32 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
35 while((s = br.readLine()) != null) {
36 if (sinceLastRepeat != null) sinceLastRepeat.addElement(s);
37 String trimmed = s.trim();
38 if (trimmed.startsWith("//#define ")) {
39 trimmed = trimmed.substring(10).trim();
40 if (trimmed.indexOf('(') >= 0 && trimmed.indexOf('(') < trimmed.indexOf(' ')) {
41 JSFunctionMacro fm = new JSFunctionMacro();
42 String key = trimmed.substring(0, trimmed.indexOf('('));
43 String unbound = trimmed.substring(trimmed.indexOf('(') + 1, trimmed.indexOf(')'));
44 if (unbound.indexOf(',') == -1) {
45 fm.unbound1 = unbound;
47 fm.unbound1 = unbound.substring(0, unbound.indexOf(','));
48 fm.unbound2 = unbound.substring(unbound.indexOf(',') + 1);
50 fm.expression = trimmed.substring(trimmed.indexOf(')')+1).trim();
53 String key = trimmed.substring(0, trimmed.indexOf(' '));
54 String val = trimmed.substring(trimmed.indexOf(' ')).trim();
55 replace.put(key, val);
57 System.out.println(); // preserve line numbers
59 } else if (trimmed.startsWith("//#repeat ")) {
60 trimmed = trimmed.substring(9);
61 while(trimmed.charAt(trimmed.length() - 1) == '\\') {
62 String s2 = br.readLine().trim();
63 if (s2.startsWith("//")) s2 = s2.substring(2).trim();
65 System.out.println(); // preserve line numbers
67 StringTokenizer st = new StringTokenizer(trimmed, " ");
68 repeatreplace = (Hashtable)replace.clone();
69 while (st.hasMoreTokens()) {
70 String tok = st.nextToken().trim();
71 String key = tok.substring(0, tok.indexOf('/'));
72 String val = tok.substring(tok.indexOf('/') + 1);
73 repeatreplace.put(key, val);
75 sinceLastRepeat = new Vector();
76 System.out.println(); // preserve line numbers
78 } else if (trimmed.startsWith("//#end")) {
79 Hashtable save = replace;
80 replace = repeatreplace;
82 for(int i=0; i<sinceLastRepeat.size() - 1; i++) System.out.print(processLine((String)sinceLastRepeat.elementAt(i), true));
83 sinceLastRepeat = null;
86 } else if (trimmed.startsWith("//#switch")) {
87 String expr = trimmed.substring(trimmed.indexOf('(') + 1, trimmed.lastIndexOf(')'));
88 System.out.println("final String neverUseThis = (String)("+expr+"); switch(neverUseThis.length()) {");
89 Hashtable[] byLength = new Hashtable[255];
91 String Default = null;
92 for(trimmed = br.readLine().trim(); !trimmed.startsWith("//#end"); trimmed = br.readLine().trim()) {
93 if (trimmed.startsWith("default:")) {
94 Default = processLine(trimmed.substring(8), false);
97 if (trimmed.startsWith("case ")) {
98 trimmed = trimmed.substring(trimmed.indexOf('\"') + 1);
99 key = trimmed.substring(0, trimmed.indexOf('\"'));
100 Hashtable thisCase = (Hashtable)byLength[key.length()];
101 if (thisCase == null) byLength[key.length()] = thisCase = new Hashtable();
102 thisCase.put(key, "");
103 trimmed = trimmed.substring(trimmed.indexOf('\"') + 1);
104 trimmed = trimmed.substring(trimmed.indexOf(':') + 1);
107 Hashtable hash = byLength[key.length()];
108 hash.put(key, (String)hash.get(key) + processLine(trimmed, false) + "\n");
110 System.out.print(processLine(trimmed, false));
114 for(int i=0; i<255; i++) {
115 if (byLength[i] == null) continue;
116 System.out.println("case " + i + ": { switch(neverUseThis.charAt(0)) {");
117 buildTrie("", byLength[i]);
118 System.out.println("}; break; }");
120 if (Default != null) System.out.println("default: { " + Default + " }");
121 System.out.println("} //switch");
124 System.out.print(processLine(s, false));
131 static void buildTrie(String prefix, Hashtable cases) {
132 Enumeration caseKeys = cases.keys();
133 Vec keys = new Vec();
134 while(caseKeys.hasMoreElements()) keys.addElement(caseKeys.nextElement());
135 keys.sort(new Vec.CompareFunc() { public int compare(Object a, Object b) {
136 return ((String)a).compareTo((String)b);
139 for(int i=0; i<keys.size(); i++) {
140 if (!((String)keys.elementAt(i)).startsWith(prefix)) continue;
141 String prefixPlusOne = ((String)keys.elementAt(i)).substring(0, prefix.length() + 1);
142 if (i<keys.size()-1 && prefixPlusOne.equals((((String)keys.elementAt(i + 1)).substring(0, prefix.length() + 1)))) {
143 System.out.println("case \'" + prefixPlusOne.charAt(prefixPlusOne.length() - 1) + "\': {");
144 System.out.println("switch(neverUseThis.charAt(" + prefix.length() + ")) {");
145 buildTrie(prefixPlusOne, cases);
146 System.out.println("} break; }");
147 while(i<keys.size() && prefixPlusOne.equals(((String)keys.elementAt(i)).substring(0, prefix.length() + 1))) i++;
148 if (i<keys.size()) { i--; continue; }
150 System.out.println("case \'" + prefixPlusOne.charAt(prefixPlusOne.length() - 1) + "\':");
151 String code = (String)cases.get(keys.elementAt(i));
152 code = code.substring(0, code.length() - 1);
153 String key = (String)keys.elementAt(i);
154 System.out.println("if (\""+key+"\".equals(neverUseThis)) { " + code + " } break; ");
159 static String processLine(String s, boolean deleteLineEndings) throws IOException {
160 if (deleteLineEndings && s.indexOf("//") != -1) s = s.substring(0, s.indexOf("//"));
162 for(int i=0; i<s.length(); i++) {
163 char c = s.charAt(i);
164 if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_') {
169 for(j = i; j < s.length(); j++) {
171 if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_') break;
173 String tok = s.substring(i, j);
174 Object val = replace.get(tok);
178 } else if (val instanceof JSFunctionMacro) {
179 if (s.charAt(j) != '(') System.err.println("open paren must follow macro binding for macro " + tok);
180 ret += ((JSFunctionMacro)val).process(s.substring(j+1, s.indexOf(')', j)));
181 i = s.indexOf(')', j);
187 if (!deleteLineEndings) ret += "\n";
191 public static class JSFunctionMacro {
192 public String unbound1 = null;
193 public String unbound2 = null;
194 public String expression = null;
195 public String process(String args) {
196 String bound1 = null;
197 String bound2 = null;
198 if (unbound2 == null) {
200 return expression.replaceAll(unbound1, bound1);
202 bound1 = args.substring(0, args.indexOf(','));
203 bound2 = args.substring(args.indexOf(',') + 1);
204 return (expression.replaceAll(unbound1, bound1).replaceAll(unbound2, bound2));