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}