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}