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