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.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022
023/**
024 * A macro object.
025 *
026 * This encapsulates a name, an argument count, and a token stream
027 * for replacement. The replacement token stream may contain the
028 * extra tokens {@link Token#M_ARG} and {@link Token#M_STRING}.
029 */
030public class Macro {
031
032    private Source source;
033    private String name;
034    /* It's an explicit decision to keep these around here. We don't
035     * need to; the argument token type is M_ARG and the value
036     * is the index. The strings themselves are only used in
037     * stringification of the macro, for debugging. */
038    private List<String> args;
039    private boolean variadic;
040    private List<Token> tokens;
041
042    public Macro(Source source, String name) {
043        this.source = source;
044        this.name = name;
045        this.args = null;
046        this.variadic = false;
047        this.tokens = new ArrayList<Token>();
048    }
049
050    public Macro(String name) {
051        this(null, name);
052    }
053
054    /**
055     * Sets the Source from which this macro was parsed.
056     */
057    public void setSource(Source s) {
058        this.source = s;
059    }
060
061    /**
062     * Returns the Source from which this macro was parsed.
063     *
064     * This method may return null if the macro was not parsed
065     * from a regular file.
066     */
067    public Source getSource() {
068        return source;
069    }
070
071    /**
072     * Returns the name of this macro.
073     */
074    public String getName() {
075        return name;
076    }
077
078    /**
079     * Sets the arguments to this macro.
080     */
081    public void setArgs(List<String> args) {
082        this.args = args;
083    }
084
085    /**
086     * Returns true if this is a function-like macro.
087     */
088    public boolean isFunctionLike() {
089        return args != null;
090    }
091
092    /**
093     * Returns the number of arguments to this macro.
094     */
095    public int getArgs() {
096        return args.size();
097    }
098
099    /**
100     * Sets the variadic flag on this Macro.
101     */
102    public void setVariadic(boolean b) {
103        this.variadic = b;
104    }
105
106    /**
107     * Returns true if this is a variadic function-like macro.
108     */
109    public boolean isVariadic() {
110        return variadic;
111    }
112
113    /**
114     * Adds a token to the expansion of this macro.
115     */
116    public void addToken(Token tok) {
117        this.tokens.add(tok);
118    }
119
120    /**
121     * Adds a "paste" operator to the expansion of this macro.
122     *
123     * A paste operator causes the next token added to be pasted
124     * to the previous token when the macro is expanded.
125     * It is an error for a macro to end with a paste token.
126     */
127    public void addPaste(Token tok) {
128        /*
129         * Given: tok0 ## tok1
130         * We generate: M_PASTE, tok0, tok1
131         * This extends as per a stack language:
132         * tok0 ## tok1 ## tok2 ->
133         *   M_PASTE, tok0, M_PASTE, tok1, tok2
134         */
135        this.tokens.add(tokens.size() - 1, tok);
136    }
137
138    /* pp */ List<Token> getTokens() {
139        return tokens;
140    }
141
142    /* Paste tokens are inserted before the first of the two pasted
143     * tokens, so it's a kind of bytecode notation. This method
144     * swaps them around again. We know that there will never be two
145     * sequential paste tokens, so a boolean is sufficient. */
146    public String getText() {
147        StringBuilder buf = new StringBuilder();
148        boolean paste = false;
149        for (Token tok : tokens) {
150            if (tok.getType() == Token.M_PASTE) {
151                assert paste == false : "Two sequential pastes.";
152                paste = true;
153                continue;
154            } else {
155                buf.append(tok.getText());
156            }
157            if (paste) {
158                buf.append(" #" + "# ");
159                paste = false;
160            }
161            // buf.append(tokens.get(i));
162        }
163        return buf.toString();
164    }
165
166    @Override
167    public String toString() {
168        StringBuilder buf = new StringBuilder(name);
169        if (args != null) {
170            buf.append('(');
171            Iterator<String> it = args.iterator();
172            while (it.hasNext()) {
173                buf.append(it.next());
174                if (it.hasNext())
175                    buf.append(", ");
176                else if (isVariadic())
177                    buf.append("...");
178            }
179            buf.append(')');
180        }
181        if (!tokens.isEmpty()) {
182            buf.append(" => ").append(getText());
183        }
184        return buf.toString();
185    }
186
187}