001/* 002 * Anarres C Preprocessor 003 * Copyright (c) 2007-2015, Shevek 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 014 * or implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.anarres.cpp; 018 019import java.io.Closeable; 020import java.io.IOException; 021import java.util.Iterator; 022import javax.annotation.CheckForNull; 023import javax.annotation.Nonnegative; 024import javax.annotation.Nonnull; 025import static org.anarres.cpp.Token.CCOMMENT; 026import static org.anarres.cpp.Token.CPPCOMMENT; 027import static org.anarres.cpp.Token.EOF; 028import static org.anarres.cpp.Token.NL; 029import static org.anarres.cpp.Token.WHITESPACE; 030 031/** 032 * An input to the Preprocessor. 033 * 034 * Inputs may come from Files, Strings or other sources. The 035 * preprocessor maintains a stack of Sources. Operations such as 036 * file inclusion or token pasting will push a new source onto 037 * the Preprocessor stack. Sources pop from the stack when they 038 * are exhausted; this may be transparent or explicit. 039 * 040 * BUG: Error messages are not handled properly. 041 */ 042public abstract class Source implements Iterable<Token>, Closeable { 043 044 private Source parent; 045 private boolean autopop; 046 private PreprocessorListener listener; 047 private boolean active; 048 private boolean werror; 049 050 /* LineNumberReader */ 051 052 /* 053 // We can't do this, since we would lose the LexerException 054 private class Itr implements Iterator { 055 private Token next = null; 056 private void advance() { 057 try { 058 if (next != null) 059 next = token(); 060 } 061 catch (IOException e) { 062 throw new UnsupportedOperationException( 063 "Failed to advance token iterator: " + 064 e.getMessage() 065 ); 066 } 067 } 068 public boolean hasNext() { 069 return next.getType() != EOF; 070 } 071 public Token next() { 072 advance(); 073 Token t = next; 074 next = null; 075 return t; 076 } 077 public void remove() { 078 throw new UnsupportedOperationException( 079 "Cannot remove tokens from a Source." 080 ); 081 } 082 } 083 */ 084 public Source() { 085 this.parent = null; 086 this.autopop = false; 087 this.listener = null; 088 this.active = true; 089 this.werror = false; 090 } 091 092 /** 093 * Sets the parent source of this source. 094 * 095 * Sources form a singly linked list. 096 */ 097 /* pp */ void setParent(Source parent, boolean autopop) { 098 this.parent = parent; 099 this.autopop = autopop; 100 } 101 102 /** 103 * Returns the parent source of this source. 104 * 105 * Sources form a singly linked list. 106 */ 107 /* pp */ final Source getParent() { 108 return parent; 109 } 110 111 112 // @OverrideMustInvoke 113 /* pp */ void init(Preprocessor pp) { 114 setListener(pp.getListener()); 115 this.werror = pp.getWarnings().contains(Warning.ERROR); 116 } 117 118 /** 119 * Sets the listener for this Source. 120 * 121 * Normally this is set by the Preprocessor when a Source is 122 * used, but if you are using a Source as a standalone object, 123 * you may wish to call this. 124 */ 125 public void setListener(PreprocessorListener pl) { 126 this.listener = pl; 127 } 128 129 /** 130 * Returns the File currently being lexed. 131 * 132 * If this Source is not a {@link FileLexerSource}, then 133 * it will ask the parent Source, and so forth recursively. 134 * If no Source on the stack is a FileLexerSource, returns null. 135 */ 136 @CheckForNull 137 public String getPath() { 138 Source parent = getParent(); 139 if (parent != null) 140 return parent.getPath(); 141 return null; 142 } 143 144 /** 145 * Returns the human-readable name of the current Source. 146 */ 147 @CheckForNull 148 public String getName() { 149 Source parent = getParent(); 150 if (parent != null) 151 return parent.getName(); 152 return null; 153 } 154 155 /** 156 * Returns the current line number within this Source. 157 */ 158 @Nonnegative 159 public int getLine() { 160 Source parent = getParent(); 161 if (parent == null) 162 return 0; 163 return parent.getLine(); 164 } 165 166 /** 167 * Returns the current column number within this Source. 168 */ 169 public int getColumn() { 170 Source parent = getParent(); 171 if (parent == null) 172 return 0; 173 return parent.getColumn(); 174 } 175 176 /** 177 * Returns true if this Source is expanding the given macro. 178 * 179 * This is used to prevent macro recursion. 180 */ 181 /* pp */ boolean isExpanding(@Nonnull Macro m) { 182 Source parent = getParent(); 183 if (parent != null) 184 return parent.isExpanding(m); 185 return false; 186 } 187 188 /** 189 * Returns true if this Source should be transparently popped 190 * from the input stack. 191 * 192 * Examples of such sources are macro expansions. 193 */ 194 /* pp */ boolean isAutopop() { 195 return autopop; 196 } 197 198 /** 199 * Returns true if this source has line numbers. 200 */ 201 /* pp */ boolean isNumbered() { 202 return false; 203 } 204 205 /* This is an incredibly lazy way of disabling warnings when 206 * the source is not active. */ 207 /* pp */ void setActive(boolean b) { 208 this.active = b; 209 } 210 211 /* pp */ boolean isActive() { 212 return active; 213 } 214 215 /** 216 * Returns the next Token parsed from this input stream. 217 * 218 * @see Token 219 */ 220 @Nonnull 221 public abstract Token token() 222 throws IOException, 223 LexerException; 224 225 /** 226 * Returns a token iterator for this Source. 227 */ 228 @Override 229 public Iterator<Token> iterator() { 230 return new SourceIterator(this); 231 } 232 233 /** 234 * Skips tokens until the end of line. 235 * 236 * @param white true if only whitespace is permitted on the 237 * remainder of the line. 238 * @return the NL token. 239 */ 240 @Nonnull 241 public Token skipline(boolean white) 242 throws IOException, 243 LexerException { 244 for (;;) { 245 Token tok = token(); 246 switch (tok.getType()) { 247 case EOF: 248 /* There ought to be a newline before EOF. 249 * At least, in any skipline context. */ 250 /* XXX Are we sure about this? */ 251 warning(tok.getLine(), tok.getColumn(), 252 "No newline before end of file"); 253 return new Token(NL, 254 tok.getLine(), tok.getColumn(), 255 "\n"); 256 // return tok; 257 case NL: 258 /* This may contain one or more newlines. */ 259 return tok; 260 case CCOMMENT: 261 case CPPCOMMENT: 262 case WHITESPACE: 263 break; 264 default: 265 /* XXX Check white, if required. */ 266 if (white) 267 warning(tok.getLine(), tok.getColumn(), 268 "Unexpected nonwhite token"); 269 break; 270 } 271 } 272 } 273 274 protected void error(int line, int column, String msg) 275 throws LexerException { 276 if (listener != null) 277 listener.handleError(this, line, column, msg); 278 else 279 throw new LexerException("Error at " + line + ":" + column + ": " + msg); 280 } 281 282 protected void warning(int line, int column, String msg) 283 throws LexerException { 284 if (werror) 285 error(line, column, msg); 286 else if (listener != null) 287 listener.handleWarning(this, line, column, msg); 288 else 289 throw new LexerException("Warning at " + line + ":" + column + ": " + msg); 290 } 291 292 public void close() 293 throws IOException { 294 } 295 296}