Refactor Ruby & Python Contrlo flow building

This commit is contained in:
Oleg Shpynov
2010-01-21 15:19:26 +03:00
parent a5575d520f
commit fe4a8b311e
7 changed files with 429 additions and 0 deletions
@@ -0,0 +1,26 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.codeInsight.controlflow;
import com.intellij.psi.PsiElement;
/**
* @author oleg
*/
public interface ConditionalInstruction extends Instruction {
boolean getResult();
PsiElement getCondition();
}
@@ -0,0 +1,8 @@
package com.intellij.codeInsight.controlflow;
/**
* @author oleg
*/
public interface ControlFlow {
Instruction[] getInstructions();
}
@@ -0,0 +1,199 @@
package com.intellij.codeInsight.controlflow;
import com.intellij.codeInsight.controlflow.impl.ConditionalInstructionImpl;
import com.intellij.codeInsight.controlflow.impl.ControlFlowImpl;
import com.intellij.codeInsight.controlflow.impl.InstructionImpl;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author oleg
*/
public class ControlFlowBuilder {
// Here we store all the instructions
public List<Instruction> instructions;
public Instruction prevInstruction;
// Here we store all the pending instructions with their scope
public List<Pair<PsiElement, Instruction>> pending;
public int instructionCount;
public ControlFlowBuilder() {
instructions = new ArrayList<Instruction>();
pending = new ArrayList<Pair<PsiElement, Instruction>>();
instructionCount = 0;
}
@Nullable
public Instruction findInstructionByElement(final PsiElement element) {
for (int i = instructions.size() - 1; i >= 0; i--) {
final Instruction instruction = instructions.get(i);
if (element.equals(instruction.getElement())) {
return instruction;
}
}
return null;
}
/**
* Adds edge between 2 edges
*
* @param beginInstruction Begin of new edge
* @param endInstruction End of new edge
*/
public void addEdge(final Instruction beginInstruction, final Instruction endInstruction) {
if (beginInstruction == null || endInstruction == null) {
return;
}
if (!beginInstruction.allSucc().contains(endInstruction)) {
beginInstruction.allSucc().add(endInstruction);
}
if (!endInstruction.allPred().contains(beginInstruction)) {
endInstruction.allPred().add(beginInstruction);
}
}
/**
* Add new node and set prev instruction pointing to this instruction
*
* @param instruction new instruction
*/
public void addNode(final Instruction instruction) {
instructions.add(instruction);
if (prevInstruction != null) {
addEdge(prevInstruction, instruction);
}
prevInstruction = instruction;
}
/**
* Stops control flow, used for break, next, redo
*/
public void flowAbrupted() {
prevInstruction = null;
}
/**
* Adds pending edge in pendingScope
*
* @param pendingScope Scope for instruction
* @param instruction "Last" pending instruction
*/
public void addPendingEdge(final PsiElement pendingScope, final Instruction instruction) {
if (instruction == null) {
return;
}
int i = 0;
// another optimization! Place pending before first scope, not contained in pendingScope
// the same logic is used in checkPending
if (pendingScope != null) {
for (; i < pending.size(); i++) {
final Pair<PsiElement, Instruction> pair = pending.get(i);
final PsiElement scope = pair.getFirst();
if (scope == null) {
continue;
}
if (!PsiTreeUtil.isAncestor(scope, pendingScope, true)) {
break;
}
}
}
pending.add(i, Pair.create(pendingScope, instruction));
}
public void checkPending(@NotNull final Instruction instruction) {
final PsiElement element = instruction.getElement();
if (element == null) {
// if element is null (fake element, we just process all pending)
for (Pair<PsiElement, Instruction> pair : pending) {
addEdge(pair.getSecond(), instruction);
}
pending.clear();
}
else {
// else we just all the pending with scope containing in element
// reverse order is just an optimization
for (int i = pending.size() - 1; i >= 0; i--) {
final Pair<PsiElement, Instruction> pair = pending.get(i);
final PsiElement scopeWhenToAdd = pair.getFirst();
if (scopeWhenToAdd == null) {
continue;
}
if (!PsiTreeUtil.isAncestor(scopeWhenToAdd, element, false)) {
addEdge(pair.getSecond(), instruction);
pending.remove(i);
}
else {
break;
}
}
}
}
/**
* Creates instruction for given element, and adds it to myInstructionsStack
* Warning! Always call finishNode after startNode
*
* @param element Element to create instruction for
* @return new instruction
*/
public Instruction startNode(final PsiElement element) {
final Instruction instruction = new InstructionImpl(this, element);
addNode(instruction);
checkPending(instruction);
return instruction;
}
/**
* Creates conditional instruction for given element, and adds it to myInstructionsStack
* Warning! Always call finishNode after startNode
*
* @param element Element to create instruction for
* @return new instruction
*/
public Instruction startConditionalNode(final PsiElement element, final PsiElement condition, final boolean result) {
final ConditionalInstruction instruction = new ConditionalInstructionImpl(this, element, condition, result);
addNode(instruction);
checkPending(instruction);
return instruction;
}
public ControlFlow build(final PsiElementVisitor visitor, final PsiElement element) {
// create start pseudo node
startNode(null);
element.acceptChildren(visitor);
// create end pseudo node and close all pending edges
checkPending(startNode(null));
final List<Instruction> result = instructions;
return new ControlFlowImpl(result.toArray(new Instruction[result.size()]));
}
public static interface PendingProcessor {
void process(PsiElement pendingScope, Instruction instruction);
}
public void processPending(final PendingProcessor processor) {
final List<Pair<PsiElement, Instruction>> pending = this.pending;
this.pending = new ArrayList<Pair<PsiElement, Instruction>>();
for (Pair<PsiElement, Instruction> pair : pending) {
processor.process(pair.getFirst(), pair.getSecond());
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.codeInsight.controlflow;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author oleg
*/
public interface Instruction {
@Nullable
PsiElement getElement();
Collection<Instruction> allSucc();
Collection<Instruction> allPred();
String getElementPresentation();
int num();
}
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.codeInsight.controlflow.impl;
import com.intellij.codeInsight.controlflow.ControlFlowBuilder;
import com.intellij.codeInsight.controlflow.ConditionalInstruction;
import com.intellij.psi.PsiElement;
/**
* @author oleg
*/
public class ConditionalInstructionImpl extends InstructionImpl implements ConditionalInstruction {
private final PsiElement myCondition;
private final boolean myResult;
public ConditionalInstructionImpl(final ControlFlowBuilder builder,
final PsiElement element,
final PsiElement condition,
final boolean result) {
super(builder, element);
myCondition = condition;
myResult = result;
}
public PsiElement getCondition() {
return myCondition;
}
public boolean getResult() {
return myResult;
}
@Override
public String toString() {
return super.toString() + ". Condition: " + myCondition.getText() + ":" + myResult;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.codeInsight.controlflow.impl;
import com.intellij.codeInsight.controlflow.ControlFlow;
import com.intellij.codeInsight.controlflow.Instruction;
/**
* @author oleg
*/
public class ControlFlowImpl implements ControlFlow {
private final Instruction[] myInstructions;
public ControlFlowImpl(Instruction[] instructions) {
myInstructions = instructions;
}
public Instruction[] getInstructions() {
return myInstructions;
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.codeInsight.controlflow.impl;
import com.intellij.codeInsight.controlflow.ControlFlowBuilder;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author oleg
*/
public class InstructionImpl implements Instruction {
final ArrayList<Instruction> myPred = new ArrayList<Instruction>();
final ArrayList<Instruction> mySucc = new ArrayList<Instruction>();
protected final PsiElement myElement;
private final int myNumber;
@Nullable
public final PsiElement getElement() {
return myElement;
}
public InstructionImpl(final ControlFlowBuilder builder, final PsiElement element) {
myElement = element;
myNumber = builder.instructionCount++;
}
public final Collection<Instruction> allSucc() {
return mySucc;
}
public final Collection<Instruction> allPred() {
return myPred;
}
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(myNumber);
builder.append("(");
for (int i = 0; i < mySucc.size(); i++) {
if (i > 0) builder.append(',');
builder.append(mySucc.get(i).num());
}
builder.append(") ").append(getElementPresentation());
return builder.toString();
}
public String getElementPresentation() {
final StringBuffer buffer = new StringBuffer();
buffer.append("element: ").append(myElement);
return buffer.toString();
}
public final int num() {
return myNumber;
}
}