All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.sass.internal.parser.FormalArgumentList Maven / Gradle / Ivy

There is a newer version: 0.9.13
Show newest version
/*
 * Copyright 2000-2014 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.sass.internal.parser;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.vaadin.sass.internal.tree.VariableNode;

/**
 * FormalArgumentList is used for representing the parameter list of a mixin or
 * a function definition. Formal arguments are always named and may optionally
 * have a default value. FormalArgumentList also supports variable arguments,
 * which means that if there are more actual than formal parameters, all actual
 * parameters not corresponding to an ordinary formal parameter are packed into
 * a list. The list becomes the value of the variable argument. When variable
 * arguments are used, the number of actual arguments can also be one less than
 * the number of formal arguments. In that case the value of the variable
 * argument is an empty list.
 * 
 * @author Vaadin
 * 
 */
public class FormalArgumentList implements Serializable, Iterable {

    private ArrayList arglist;
    private String variableArgumentName = null;

    public FormalArgumentList(Collection args,
            boolean hasVariableArguments) {
        if (args != null) {
            arglist = new ArrayList(args);
            if (hasVariableArguments) {
                if (arglist.size() == 0) {
                    throw new ParseException(
                            "Variable arguments are not allowed with an empty formal parameter list.");
                }
                variableArgumentName = arglist.get(args.size() - 1).getName();
            }
        } else {
            arglist = new ArrayList();
        }
    }

    public FormalArgumentList replaceVariables(
            Collection variables) {
        ArrayList result = new ArrayList();
        for (final VariableNode arg : arglist) {
            SassListItem expr = arg.getExpr();
            for (final VariableNode var : variables) {
                if (arg.getName().equals(var.getName()) && expr == null) {
                    expr = var.getExpr();
                    break;
                }
            }
            result.add(new VariableNode(arg.getName(), expr, false));
        }
        return new FormalArgumentList(result, hasVariableArguments());
    }

    /**
     * Returns a new FormalArgumentList that is obtained from this list by
     * replacing all formal arguments with the corresponding actual arguments.
     * Does not modify this list.
     * 
     * The replacement works in several phases. The first phase replaces formal
     * arguments that have the same name as one of the actual arguments. The
     * second phase replaces the remaining unset formal arguments with the
     * values of the unnamed actual arguments. Default values are then given for
     * any remaining formal arguments without a value. Finally, if there are
     * variable arguments, any unused actual arguments are packed into a list,
     * which becomes the value of the variable argument.
     * 
     * Examples:
     * 
     * 1) formal argument list ($a, $b, $c: 10, $d: 7, $e: 5), actual argument
     * list (1, 2, $d: 4, $c: 3). After the first phase the list is ($a: null,
     * $b: null, $c: 3, $d: 4, $e: null). The second phase transforms that to
     * ($a: 1, $b: 2, $c: 3, $d: 4, $e: null). The remaining variable is then
     * given its default value with result ($a: 1, $b: 2, $c: 3, $d: 4, $e: 5).
     * 
     * 2) formal argument list ($a...), actual argument list empty. After
     * replacement the formal argument list is ($a: ()).
     * 
     * 3) formal argument list ($a, $b...), actual argument list (1, 2, 3, $c:
     * 4). After replacement the formal argument list is ($a: 1, $b: (2, 3, $c:
     * 4).
     * 
     * Note that if named arguments are packed into a list for a variable
     * argument as in example 3, they cannot be accessed by the mixin or the
     * function. It is, however, possible to pass them to another function or a
     * mixin using an @include or a function call with variable arguments.
     * 
     * @param actualArgumentList
     *            The actual arguments.
     * @return A FormalArgumentList with the values of the arguments taken from
     *         the actual argument list and from the default values.
     */
    public FormalArgumentList replaceFormalArguments(
            ActualArgumentList actualArgumentList) {
        ArrayList result = initializeArgumentList(arglist);
        List unusedNamedActual = replaceNamedArguments(result,
                actualArgumentList);
        List unusedUnnamedActual = replaceUnnamedAndDefaultArguments(
                result, actualArgumentList);
        // Perform sanity checks and handle variable arguments
        if (hasVariableArguments()) {
            ArgumentList varArgContents = new ArgumentList(
                    actualArgumentList.getSeparator(), unusedUnnamedActual,
                    unusedNamedActual);
            VariableNode varArg = new VariableNode(variableArgumentName,
                    varArgContents, false);
            result.set(result.size() - 1, varArg);
        } else {
            if (!unusedNamedActual.isEmpty() || !unusedUnnamedActual.isEmpty()) {
                throw new ParseException(
                        "Substitution error: all actual parameters were not used. Formal parameters: "
                                + this + ", actual parameters: "
                                + actualArgumentList, actualArgumentList);
            }
        }
        return new FormalArgumentList(result, false);
    }

    /**
     * Replaces the expressions of the formal arguments corresponding to the
     * named actual arguments. If variable arguments are used, the variable
     * argument is not replaced here. Instead, the actual name-value pair with
     * the name of the variable argument is added to the list of unused named
     * actual arguments. Note that this is an implicit argument for the method;
     * it is used for error reporting and for accessing the variable argument
     * information.
     * 
     * Modifies formalArguments.
     * 
     * Examples: a) formal arguments ($a, $b, $c), actual arguments (1, $c:3,
     * $b:2). After replacing with named actual arguments the formal argument
     * list is ($a, $b: 2, $c: 3). b) formal arguments ($a, $b...), actual
     * arguments (1, 2, $c: 3). No named variables are replaced here, but a list
     * of unused named arguments is returned, i.e. the list ($c: 3).
     * 
     * @return A list of actual arguments for which no corresponding formal
     *         argument was found. This is allowed to be nonempty when variable
     *         arguments are used.
     */
    private List replaceNamedArguments(
            ArrayList formalArguments,
            ActualArgumentList actualArguments) {
        ArrayList unusedNamed = new ArrayList();
        for (VariableNode actualNode : actualArguments.getNamedVariables()) {
            boolean actualUsed = false;
            for (VariableNode formalNode : formalArguments) {
                if (formalNode.getName().equals(actualNode.getName())
                        && !actualNode.getName().equals(variableArgumentName)) {
                    if (formalNode.getExpr() != null) {
                        throw new ParseException(
                                "The named argument $"
                                        + formalNode.getName()
                                        + "appears more than once in the actual argument list: "
                                        + actualArguments, actualArguments);
                    }
                    actualUsed = true;
                    formalNode.setExpr(actualNode.getExpr());
                }
            }
            if (!actualUsed) {
                if (!hasVariableArguments()) {
                    throw new ParseException(
                            "There is no formal argument corresponding to the actual argument "
                                    + actualNode.getName()
                                    + " in the formal argument list " + this,
                            actualArguments);
                }
                unusedNamed.add(actualNode);
            }
        }
        return unusedNamed;
    }

    /**
     * Replaces the arguments that are specified by position, i.e. the unnamed
     * arguments. Also sets the default values for formal arguments that still
     * have null value after replacing the arguments. This is an implicit
     * parameter for the method; it is used for error reporting and for
     * accessing the default values.
     * 
     * Modifies formalArguments.
     * 
     * The replacement is done in left-to-right order, i.e. the first unset
     * formal argument gets the value of the first unnnamed actual argument.
     * 
     * Example: actual arguments ($a, $b: 2, $c: 6), actual argument list (1,
     * $c:3). The named arguments have already been replaced so this method gets
     * the formal argument list ($a: null, $b: null, $c: 3) as its input.
     * Replacing the positional argument yields ($a: 1, $b: null, $c: 3) and
     * replacing the default value the list ($a: 1, $b: 2, $c: 3).
     * 
     * @param formalArguments
     *            The formal arguments.
     * @param actualArguments
     *            The actual arguments.
     * @return A list of unused unnamed parameters. This may be nonempty when
     *         variable arguments are used.
     */
    private List replaceUnnamedAndDefaultArguments(
            ArrayList formalArguments,
            ActualArgumentList actualArguments) {
        // Replace unnamed arguments
        int formalIndex = getNextUnset(formalArguments, 0);
        int actualIndex = 0;
        int maxFormalIndex = hasVariableArguments() ? formalArguments.size() - 1
                : formalArguments.size();
        while (formalIndex < maxFormalIndex
                && actualIndex < actualArguments.size()) {
            formalArguments.get(formalIndex).setExpr(
                    actualArguments.get(actualIndex));
            formalIndex = getNextUnset(formalArguments, formalIndex + 1);
            actualIndex++;
        }
        // Replace default values given in the definition node
        while (formalIndex < maxFormalIndex) {
            if (arglist.get(formalIndex).getExpr() == null) {
                throw new ParseException(
                        "Argument substitution error: there is no value for the argument "
                                + formalArguments.get(formalIndex).getName()
                                + ". Formal arguments: " + this
                                + ", actual arguments: " + actualArguments,
                        actualArguments);
            }
            formalArguments.get(formalIndex).setExpr(
                    arglist.get(formalIndex).getExpr());
            formalIndex = getNextUnset(formalArguments, formalIndex + 1);
        }
        ArrayList remainingUnnamed = subList(actualArguments,
                actualIndex, actualArguments.size());
        return remainingUnnamed;
    }

    private static ArrayList subList(ActualArgumentList list,
            int minIndex, int maxIndex) {
        ArrayList result = new ArrayList();
        for (int i = minIndex; i < maxIndex; i++) {
            result.add(list.get(i));
        }
        return result;
    }

    private static int getNextUnset(ArrayList named, int i) {
        while (i < named.size() && named.get(i).getExpr() != null) {
            i++;
        }
        return i;
    }

    private static ArrayList initializeArgumentList(
            List namedParameters) {
        ArrayList result = new ArrayList();
        for (VariableNode node : namedParameters) {
            result.add(new VariableNode(node.getName(), null, false));
        }
        return result;
    }

    public boolean hasVariableArguments() {
        return variableArgumentName != null;
    }

    public boolean isEmpty() {
        return arglist.isEmpty();
    }

    @Override
    public Iterator iterator() {
        return arglist.iterator();
    }

    public int size() {
        return arglist.size();
    }

    public VariableNode get(int i) {
        return arglist.get(i);
    }

    public List getArguments() {
        return Collections.unmodifiableList(arglist);
    }

    @Override
    public String toString() {
        String result = "FormalArgumentList[";
        for (int i = 0; i < arglist.size(); i++) {
            VariableNode item = arglist.get(i);
            result += "$" + item.getName() + ": ";
            if (item.getExpr() == null) {
                result += "null";
            } else {
                result += item.getExpr().printState();
            }
            if (i < arglist.size() - 1) {
                result += ", ";
            }
        }
        result += "]";
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy