/*
* Copyright (c) 2013 Patrick Scheibe
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.halirutan.mathematica.parsing.psi.util;
import com.google.common.collect.Lists;
import com.intellij.psi.*;
import de.halirutan.mathematica.parsing.MathematicaElementTypes;
import de.halirutan.mathematica.parsing.psi.api.FunctionCall;
import de.halirutan.mathematica.parsing.psi.api.Symbol;
import de.halirutan.mathematica.parsing.psi.api.assignment.Set;
import de.halirutan.mathematica.parsing.psi.api.assignment.SetDelayed;
import de.halirutan.mathematica.parsing.psi.api.assignment.TagSet;
import de.halirutan.mathematica.parsing.psi.api.assignment.TagSetDelayed;
import de.halirutan.mathematica.parsing.psi.api.pattern.*;
import de.halirutan.mathematica.parsing.psi.api.rules.Rule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedList;
import java.util.List;
/**
* @author patrick (5/21/13)
*/
public class MathematicaPsiUtilities {
/**
* Extracts the assignment symbol from assignment operations, <code>g[x_]:=x^2</code> should return the g and x
* <code>a = 2</code> returns a. Note that vector assignments like <code>{a,{b,c}} = {1,{2,3}}</code> return a list of
* variables.
*
* @param element
* PsiElement of the assignment
* @return List of symbols which are assigned.
*/
@Nullable
public static List<Symbol> getAssignmentSymbols(PsiElement element) {
PsiElement firstChild = element.getFirstChild();
final List<Symbol> assignees = Lists.newArrayList();
if (element instanceof SetDelayed || element instanceof Set) {
if (firstChild instanceof Symbol) {
assignees.add((Symbol) firstChild);
}
// extract f from f[arg,..] := blub
if (firstChild instanceof FunctionCall) {
// test for SubValues definition f[][] := ...
if (firstChild.getFirstChild() instanceof FunctionCall)
firstChild = firstChild.getFirstChild();
if (firstChild.getFirstChild() instanceof Symbol) {
assignees.add((Symbol) firstChild.getFirstChild());
}
} else
// extract a,b,c,d from things like {{a,b},{c,d}} = {{1,2},{3,4}}
if (firstChild instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
assignees.addAll(getSymbolsFromNestedList(firstChild));
}
} else if (element instanceof TagSetDelayed || element instanceof TagSet) {
if (firstChild instanceof Symbol) {
assignees.add((Symbol) firstChild);
}
}
return assignees;
}
public static List<Symbol> getSymbolsFromArgumentPattern(@Nullable PsiElement element) {
final LinkedList<Symbol> result = new LinkedList<Symbol>();
if (element == null) {
return result;
}
//noinspection OverlyComplexAnonymousInnerClass
PsiElementVisitor patternVisitor = new PsiRecursiveElementVisitor() {
private final List<String> myDiveInFirstChild = Lists.newArrayList("Longest", "Shortest", "Repeated", "Optional", "PatternTest", "Condition");
private final List<String> myDoNotDiveIn = Lists.newArrayList("Verbatim");
@Override
public void visitElement(PsiElement element) {
if (element instanceof Blank ||
element instanceof BlankSequence ||
element instanceof BlankNullSequence ||
element instanceof Pattern) {
PsiElement possibleSymbol = element.getFirstChild();
if (possibleSymbol instanceof Symbol) {
result.add((Symbol) possibleSymbol);
}
if (element instanceof Pattern) {
element.acceptChildren(this);
}
} else if (element instanceof Optional || element instanceof Condition || element instanceof PatternTest) {
PsiElement firstChild = element.getFirstChild();
if (firstChild != null) {
firstChild.accept(this);
}
} else if (element instanceof FunctionCall) {
PsiElement head = element.getFirstChild();
final String name = head.getNode().getText();
if (myDiveInFirstChild.contains(name)) {
List<PsiElement> args = getArguments(element);
if (args.size() > 0) {
args.get(0).accept(this);
}
} else if (!myDoNotDiveIn.contains(name)) {
element.acceptChildren(this);
}
} else {
element.acceptChildren(this);
}
}
};
patternVisitor.visitElement(element);
return result;
}
/**
* Simple version to extract Symbols from a nested list up to level 2. For the following examples all symbols a,b,c,d
* would be extracted successfully: <ul > <li ><code>{a,b,c,d}</code></li> <li ><code>{{a,b},c,d}</code></li> <li
* ><code>{{a},b,{c},d}</code></li> </ul>
*
* @param listHead
* List to extract from
* @return List of extracted {@link Symbol} PsiElement's.
*/
@NotNull
private static List<Symbol> getSymbolsFromNestedList(PsiElement listHead) {
List<Symbol> assignees = Lists.newArrayList();
PsiElement children[] = listHead.getChildren();
for (PsiElement child : children) {
if (child instanceof Symbol) {
assignees.add((Symbol) child);
}
if (child instanceof List) {
for (PsiElement childLevel2 : child.getChildren()) {
if (childLevel2 instanceof Symbol) {
assignees.add((Symbol) childLevel2);
}
}
}
}
return assignees;
}
/**
* This extracts the local defined arguments of a <code>Function</code> call. Examples are <ul>
* <li><code>Function[arg, arg^2]</code> extracts <code>arg</code></li> <li><code>Function[{arg1,arg2},
* arg1+arg2]</code> extracts <code>arg1,arg2</code></li> <li><code>Function[#+#]</code> extracts nothing</li>
* <li><code>Function[Null, #+#, {Listable}]</code> extracts nothing</li> </ul>
*
* @param element
* The {@link PsiElement} of the function call
* @return The set of localized function arguments
*/
public static List<Symbol> getLocalFunctionVariables(@NotNull FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && element.getScopingConstruct().equals(LocalizationConstruct.ConstructType.FUNCTION)) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 1) {
return localVariables;
}
final PsiElement firstArgument = arguments.get(0);
switch (arguments.size()) {
case 1:
break;
case 2:
if (firstArgument instanceof Symbol) {
localVariables.add((Symbol) firstArgument);
} else if (firstArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
localVariables = getSymbolsFromNestedList(firstArgument);
}
break;
case 3:
if (firstArgument instanceof Symbol && !((Symbol) firstArgument).getSymbolName().equals("Null")) {
localVariables.add((Symbol) firstArgument);
} else if (firstArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
localVariables = getSymbolsFromNestedList(firstArgument);
}
break;
}
}
return localVariables;
}
/**
* This extracts the local defined arguments of a <code>Module</code>, <code>Block</code>, ... call. Examples are <ul>
* <li><code>Module[{arg}, arg^2]</code> extracts <code>arg</code></li> <li><code>With[{a=1,b=2}, a*b]</code> extracts
* <code>a,b</code></li> </ul>
*
* @param element
* The {@link PsiElement} of the function call
* @return The set of localized function arguments
*/
public static List<Symbol> getLocalModuleLikeVariables(@NotNull FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && LocalizationConstruct.isModuleLike(element.getScopingConstruct())) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 1) {
return localVariables;
}
final PsiElement firstArgument = arguments.get(0);
if (firstArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
for (PsiElement e : firstArgument.getChildren()) {
if (e instanceof Symbol) {
localVariables.add((Symbol) e);
}
if (e instanceof Set || e instanceof SetDelayed) {
if (e.getFirstChild() instanceof Symbol) localVariables.add((Symbol) e.getFirstChild());
}
}
}
}
return localVariables;
}
/**
* This extracts the local defined arguments of a <code>Table</code>, <code>Sum</code>, ... call. Examples are <ul>
* <li><code>Table[i,{i,10}]</code> extracts <code>i</code></li> <li><code>NSum[a+b,{a,0,10},{b,0,10}]</code> extracts
* <code>a,b</code></li> </ul>
*
* @param element
* The {@link PsiElement} of the function call
* @return The set of localized function arguments
*/
public static List<Symbol> getLocalTableLikeVariables(FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && LocalizationConstruct.isTableLike(element.getScopingConstruct())) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 2) {
return localVariables;
}
for (int i = 1; i < arguments.size(); i++) {
final PsiElement currentArgument = arguments.get(i);
if (currentArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
final PsiElement firstListElement = getFirstListElement(currentArgument);
if (firstListElement instanceof Symbol) {
localVariables.add((Symbol) firstListElement);
}
}
}
}
return localVariables;
}
/**
* This extracts the local defined arguments of a <code>Manipulate</code>. There are many variations for the
* definition of a <code>Manipulate</code> variable and I'm not sure whether this works in all circumstance. What I
* haven't implemented is the usage of <code>Control[...]</code> objects.
*
* @param element
* The {@link PsiElement} of the function call
* @return The set of localized function arguments for this <code>Manipulate</code>
*/
public static List<Symbol> getLocalManipulateLikeVariables(FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && LocalizationConstruct.isManipulateLike(element.getScopingConstruct())) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 2) {
return localVariables;
}
for (int i = 1; i < arguments.size(); i++) {
final PsiElement currentArgument = arguments.get(i);
if (currentArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
final PsiElement firstListElement = getFirstListElement(currentArgument);
if (firstListElement instanceof Symbol) {
localVariables.add((Symbol) firstListElement);
} else if (firstListElement instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
final PsiElement subListArg = getFirstListElement(firstListElement);
if (subListArg instanceof Symbol) {
localVariables.add((Symbol) subListArg);
}
}
}
}
}
return localVariables;
}
/**
* This extracts the local defined arguments of a <code>Compile</code>.
*
* @param element
* The {@link PsiElement} of the function call
* @return The set of localized function arguments for this <code>Compile</code>
*/
public static List<Symbol> getLocalCompileLikeVariables(FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && LocalizationConstruct.isCompileLike(element.getScopingConstruct())) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 1) {
return localVariables;
}
final PsiElement firstArgument = arguments.get(0);
if (firstArgument instanceof Symbol) {
localVariables.add((Symbol) firstArgument);
return localVariables;
}
if (firstArgument instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
PsiElement listElement = getFirstListElement(firstArgument);
while (listElement != null) {
if (listElement instanceof de.halirutan.mathematica.parsing.psi.api.lists.List) {
final PsiElement possibleSymbol = getFirstListElement(listElement);
if (possibleSymbol instanceof Symbol) {
localVariables.add((Symbol) possibleSymbol);
}
}
listElement = getNextArgument(listElement);
}
}
}
return localVariables;
}
/**
* This extracts the local defined argument for a <code>Limit[Sin[x]/x, x-> 0]</code> call. Note that the returned
* list has always only one element since <code>Limit</code> always uses only one variable.
*
* @param element
* The {@link PsiElement} of the function call
* @return The localized argument
*/
public static List<Symbol> getLocalLimitVariables(FunctionCall element) {
List<Symbol> localVariables = Lists.newArrayList();
if (element.isScopingConstruct() && LocalizationConstruct.isLimitLike(element.getScopingConstruct())) {
final List<PsiElement> arguments = getArguments(element);
if (arguments.size() < 2) {
return localVariables;
}
final PsiElement rule = arguments.get(1);
if (rule instanceof Rule && rule.getFirstChild() instanceof Symbol) {
localVariables.add((Symbol) rule.getFirstChild());
}
}
return localVariables;
}
public static PsiElement getNextSiblingSkippingWhitespace(@Nullable PsiElement elm) {
if (elm == null) return null;
PsiElement sibling = elm.getNextSibling();
while (sibling instanceof PsiWhiteSpace || sibling instanceof PsiComment) {
sibling = sibling.getNextSibling();
}
return sibling;
}
public static PsiElement getFirstListElement(@Nullable PsiElement list) {
if (list == null) {
return null;
}
PsiElement brace = list.getFirstChild();
if (brace == null || !brace.getNode().getText().equals("{")) {
return null;
}
return getNextSiblingSkippingWhitespace(brace);
}
/**
* Takes the complete {@link PsiElement} of a function-call like <code>f[x,y,z]</code> or <code>Plot[x,{x,0,1}]</code>
* and skips over function-head, whitespaces and commas to give you only the arguments.
*
* @param func
* {@link FunctionCall} element from which you want the arguments
* @return List of arguments
*/
@NotNull
public static List<PsiElement> getArguments(@Nullable PsiElement func) {
List<PsiElement> allArguments = Lists.newLinkedList();
if (!(func instanceof FunctionCall)) {
return allArguments;
}
boolean skipHead = true;
for (PsiElement child : func.getChildren()) {
if (skipHead) {
skipHead = false;
continue;
}
allArguments.add(child);
}
return allArguments;
}
@Nullable
public static PsiElement getNextArgument(@Nullable PsiElement arg) {
if (arg == null) {
return null;
}
PsiElement comma = getNextSiblingSkippingWhitespace(arg);
if (comma != null && comma.getNode().getElementType().equals(MathematicaElementTypes.COMMA)) {
return getNextSiblingSkippingWhitespace(comma);
}
return null;
}
}