001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.compiler;
012
013import gololang.ir.*;
014
015import java.util.*;
016
017public class ClosureCaptureGoloIrVisitor extends AbstractGoloIrVisitor {
018
019  private static final class Context {
020    final Set<String> parameterReferences = new HashSet<>();
021    final Set<String> allReferences = new HashSet<>();
022    final Set<String> localReferences = new HashSet<>();
023    final Set<String> accessedReferences = new HashSet<>();
024    final Map<String, Block> definingBlock = new HashMap<>();
025    final Deque<ReferenceTable> referenceTableStack = new LinkedList<>();
026
027    Set<String> shouldBeParameters() {
028      Set<String> result = new LinkedHashSet<>();
029      for (String refName : accessedReferences) {
030        if (!localReferences.contains(refName)) {
031          result.add(refName);
032        }
033      }
034      return result;
035    }
036
037    Set<String> shouldBeRemoved() {
038      Set<String> result = new HashSet<>(allReferences);
039      for (String ref : accessedReferences) {
040        result.remove(ref);
041      }
042      return result;
043    }
044  }
045
046  private final Deque<Context> stack = new LinkedList<>();
047
048  private Context context() {
049    return stack.peek();
050  }
051
052  private void newContext() {
053    stack.push(new Context());
054  }
055
056  private void dropContext() {
057    stack.pop();
058  }
059
060  private void dropBlockTable() {
061    if (!stack.isEmpty()) {
062      context().referenceTableStack.pop();
063    }
064  }
065
066  private void pushBlockTable(Block block) {
067    if (!stack.isEmpty()) {
068      if (!context().referenceTableStack.isEmpty()) {
069        block.getReferenceTable().relink(context().referenceTableStack.peek());
070      }
071      context().referenceTableStack.push(block.getReferenceTable());
072    }
073  }
074
075  private void locallyDeclared(String name) {
076    if (!stack.isEmpty()) {
077      context().localReferences.add(name);
078    }
079  }
080
081  private void locallyAssigned(String name) {
082    if (!stack.isEmpty()) {
083      context().accessedReferences.add(name);
084    }
085  }
086
087  private void accessed(String name) {
088    if (!stack.isEmpty()) {
089      context().accessedReferences.add(name);
090    }
091  }
092
093  private void definedInBlock(Set<String> references, Block block) {
094    if (!stack.isEmpty()) {
095      for (String ref : references) {
096        context().definingBlock.put(ref, block);
097      }
098      context().allReferences.addAll(references);
099    }
100  }
101
102  private void declaredParameters(List<String> references) {
103    if (!stack.isEmpty()) {
104      context().parameterReferences.addAll(references);
105    }
106  }
107
108  @Override
109  public void visitFunction(GoloFunction function) {
110    if (function.isSynthetic()) {
111      newContext();
112      declaredParameters(function.getParameterNames());
113      function.getBlock().internReferenceTable();
114      function.walk(this);
115      function.addSyntheticParameters(context().shouldBeParameters());
116      dropUnused(context().shouldBeRemoved());
117      dropContext();
118    } else {
119      function.walk(this);
120    }
121    function.captureClosedReference();
122  }
123
124  private void dropUnused(Set<String> refs) {
125    Context context = context();
126    for (String ref : refs) {
127      if (!context.parameterReferences.contains(ref)) {
128        context.definingBlock.get(ref).getReferenceTable().remove(ref);
129      }
130    }
131  }
132
133  @Override
134  public void visitBlock(Block block) {
135    pushBlockTable(block);
136    definedInBlock(block.getReferenceTable().ownedSymbols(), block);
137    block.walk(this);
138    dropBlockTable();
139  }
140
141  @Override
142  public void visitFunctionInvocation(FunctionInvocation functionInvocation) {
143    if (context() != null) {
144      Context context = context();
145      String name = functionInvocation.getName();
146      if (context.allReferences.contains(name)) {
147        accessed(name);
148      }
149    }
150
151    functionInvocation.walk(this);
152  }
153
154  @Override
155  public void visitAssignmentStatement(AssignmentStatement assignmentStatement) {
156    LocalReference localReference = assignmentStatement.getLocalReference();
157    String referenceName = localReference.getName();
158    if (!localReference.isModuleState()) {
159      if (!stack.isEmpty()) {
160        assignmentStatement.to(context().referenceTableStack.peek().get(referenceName));
161      }
162      if (assignmentStatement.isDeclaring()) {
163        locallyDeclared(referenceName);
164      }
165    } else {
166      locallyDeclared(referenceName);
167    }
168    locallyAssigned(referenceName);
169    assignmentStatement.walk(this);
170    if (assignmentStatement.expression() instanceof ClosureReference) {
171      ClosureReference closure = (ClosureReference) assignmentStatement.expression();
172      closure.getTarget().setSyntheticSelfName(referenceName);
173    }
174  }
175
176  @Override
177  public void visitReferenceLookup(ReferenceLookup referenceLookup) {
178    accessed(referenceLookup.getName());
179  }
180
181  @Override
182  public void visitTryCatchFinally(TryCatchFinally tryCatchFinally) {
183    tryCatchFinally.getTryBlock().accept(this);
184    if (tryCatchFinally.hasCatchBlock()) {
185      locallyAssigned(tryCatchFinally.getExceptionId());
186      locallyDeclared(tryCatchFinally.getExceptionId());
187      tryCatchFinally.getCatchBlock().accept(this);
188    }
189    if (tryCatchFinally.hasFinallyBlock()) {
190      tryCatchFinally.getFinallyBlock().accept(this);
191    }
192  }
193
194  @Override
195  public void visitClosureReference(ClosureReference closureReference) {
196    closureReference.walk(this);
197    if (closureReference.getTarget().isSynthetic()) {
198      Context context = context();
199      if (context != null) {
200        for (String refName : closureReference.getTarget().getParameterNames()) {
201          ReferenceTable referenceTable = context.referenceTableStack.peek();
202          if (referenceTable.hasReferenceFor(refName)) {
203            // ...else it's a regular parameter
204            accessed(refName);
205          }
206        }
207      }
208    }
209  }
210}