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}