001/*
002 * Copyright (c) 2012-2021 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.Deque;
016import java.util.HashSet;
017import java.util.LinkedList;
018import java.util.Set;
019
020import static org.eclipse.golo.compiler.GoloCompilationException.Problem.Type.*;
021import static gololang.Messages.message;
022import static gololang.Messages.prefixed;
023
024public class LocalReferenceAssignmentAndVerificationVisitor extends AbstractGoloIrVisitor {
025
026  private GoloModule module = null;
027  private final AssignmentCounter assignmentCounter = new AssignmentCounter();
028  private final Deque<GoloFunction> functionStack = new LinkedList<>();
029  private final Deque<ReferenceTable> tableStack = new LinkedList<>();
030  private final Deque<Set<LocalReference>> assignmentStack = new LinkedList<>();
031  private GoloCompilationException.Builder exceptionBuilder;
032  private final HashSet<LocalReference> uninitializedReferences = new HashSet<>();
033
034
035  private static class AssignmentCounter {
036
037    private int counter = 0;
038
039    public int next() {
040      return counter++;
041    }
042
043    public void reset() {
044      counter = 0;
045    }
046  }
047
048  LocalReferenceAssignmentAndVerificationVisitor() { }
049
050  LocalReferenceAssignmentAndVerificationVisitor(GoloCompilationException.Builder builder) {
051    this();
052    setExceptionBuilder(builder);
053  }
054
055  public void setExceptionBuilder(GoloCompilationException.Builder builder) {
056    exceptionBuilder = builder;
057  }
058
059  private GoloCompilationException.Builder getExceptionBuilder() {
060    if (exceptionBuilder == null) {
061      exceptionBuilder = new GoloCompilationException.Builder(module.getPackageAndClass().toString());
062    }
063    return exceptionBuilder;
064  }
065
066  private void errorMessage(GoloCompilationException.Problem.Type type, GoloElement<?> node, String message) {
067    PositionInSourceCode position = null;
068    if (node != null) {
069      position = node.positionInSourceCode();
070    }
071    String errorMessage = message + ' ' + (
072        (position != null || position.isUndefined())
073        ? message("source_position", position.getStartLine(), position.getStartColumn())
074        : message("generated_code")) + ".";
075
076    getExceptionBuilder().report(type, node, errorMessage);
077  }
078
079  @Override
080  public void visitModule(GoloModule module) {
081    this.module = module;
082    module.walk(this);
083  }
084
085  @Override
086  public void visitFunction(GoloFunction function) {
087    assignmentCounter.reset();
088    functionStack.push(function);
089    ReferenceTable table = function.getBlock().getReferenceTable();
090    for (String parameterName : function.getParameterNames()) {
091      LocalReference reference = table.get(parameterName);
092      uninitializedReferences.remove(reference);
093      if (reference == null) {
094        if (!function.isSynthetic()) {
095          throw new IllegalStateException(
096              prefixed("bug", message("parameter_not_declared", parameterName, function.getName())));
097        }
098      } else {
099        reference.setIndex(assignmentCounter.next());
100      }
101    }
102    function.walk(this);
103    functionStack.pop();
104  }
105
106  @Override
107  public void visitBlock(Block block) {
108    ReferenceTable table = block.getReferenceTable();
109    extractUninitializedReferences(table);
110    tableStack.push(table);
111    assignmentStack.push(extractAssignedReferences(table));
112    block.walk(this);
113    assignmentStack.pop();
114    tableStack.pop();
115  }
116
117  private void extractUninitializedReferences(ReferenceTable table) {
118    for (LocalReference reference : table.ownedReferences()) {
119      if (reference.getIndex() < 0 && !reference.isModuleState()) {
120        reference.setIndex(assignmentCounter.next());
121        uninitializedReferences.add(reference);
122      }
123    }
124  }
125
126  private Set<LocalReference> extractAssignedReferences(ReferenceTable table) {
127    HashSet<LocalReference> assigned = new HashSet<>();
128    if (table == functionStack.peek().getBlock().getReferenceTable()) {
129      for (String param : functionStack.peek().getParameterNames()) {
130        assigned.add(table.get(param));
131      }
132    }
133    if (!assignmentStack.isEmpty()) {
134      assigned.addAll(assignmentStack.peek());
135    }
136    return assigned;
137  }
138
139  @Override
140  public void visitFunctionInvocation(FunctionInvocation functionInvocation) {
141    if (!tableStack.isEmpty()) {
142      if (tableStack.peek().hasReferenceFor(functionInvocation.getName())) {
143        if (tableStack.peek().get(functionInvocation.getName()).isModuleState()) {
144          functionInvocation.onModuleState();
145        } else {
146          functionInvocation.onReference();
147        }
148      }
149    }
150    functionInvocation.walk(this);
151  }
152
153  @Override
154  public void visitAssignmentStatement(AssignmentStatement assignmentStatement) {
155    LocalReference reference = assignmentStatement.getLocalReference();
156    Set<LocalReference> assignedReferences = assignmentStack.peek();
157    if (redeclaringReferenceInBlock(assignmentStatement, reference, assignedReferences)) {
158      errorMessage(REFERENCE_ALREADY_DECLARED_IN_BLOCK, assignmentStatement,
159          message("reference_already_declared", reference.getName()));
160    } else if (assigningConstant(reference, assignedReferences)) {
161      errorMessage(ASSIGN_CONSTANT, assignmentStatement,
162          message("assign_constant", reference.getName()));
163    }
164    bindReference(reference);
165    assignedReferences.add(reference);
166    assignmentStatement.walk(this);
167    if (assignmentStatement.isDeclaring() && !reference.isSynthetic()) {
168      uninitializedReferences.remove(reference);
169    }
170  }
171
172  private void bindReference(LocalReference reference) {
173    ReferenceTable table = tableStack.peek();
174    if (reference.getIndex() < 0) {
175      if (table.hasReferenceFor(reference.getName())) {
176        reference.setIndex(table.get(reference.getName()).getIndex());
177      } else if (reference.isSynthetic()) {
178        reference.setIndex(assignmentCounter.next());
179        table.add(reference);
180      }
181    }
182  }
183
184  private boolean redeclaringReferenceInBlock(AssignmentStatement assignmentStatement, LocalReference reference, Set<LocalReference> assignedReferences) {
185    return !reference.isSynthetic() && assignmentStatement.isDeclaring() && referenceNameExists(reference, assignedReferences);
186  }
187
188  private boolean assigningConstant(LocalReference reference, Set<LocalReference> assignedReferences) {
189    return reference.isConstant() && (
190        assignedReferences.contains(reference)
191        || (reference.isModuleState() && !functionStack.peek().isModuleInit()));
192  }
193
194  private boolean referenceNameExists(LocalReference reference, Set<LocalReference> referencesInBlock) {
195    for (LocalReference ref : referencesInBlock) {
196      if ((ref != null) && ref.getName().equals(reference.getName())) {
197        return true;
198      }
199    }
200    return false;
201  }
202
203  @Override
204  public void visitReferenceLookup(ReferenceLookup referenceLookup) {
205    ReferenceTable table = tableStack.peek();
206    if (table == null) { return; }
207    if (!table.hasReferenceFor(referenceLookup.getName())) {
208      errorMessage(UNDECLARED_REFERENCE, referenceLookup,
209          message("undeclared_reference", referenceLookup.getName(),
210          !functionStack.isEmpty()
211            ? message("in_function", functionStack.peek().getName())
212            : ""));
213    }
214    LocalReference ref = referenceLookup.resolveIn(table);
215    if (isUninitialized(ref)) {
216      errorMessage(UNINITIALIZED_REFERENCE_ACCESS, referenceLookup,
217          message("uninitialized_reference_access", ref.getName()));
218    }
219  }
220
221  private boolean isUninitialized(LocalReference ref) {
222    return ref != null && !ref.isSynthetic() && !ref.isModuleState() && uninitializedReferences.contains(ref);
223  }
224
225  @Override
226  public void visitClosureReference(ClosureReference closureReference) { }
227
228  @Override
229  public void visitLoopBreakFlowStatement(LoopBreakFlowStatement loopBreakFlowStatement) {
230    if (loopBreakFlowStatement.getEnclosingLoop() == null) {
231      errorMessage(BREAK_OR_CONTINUE_OUTSIDE_LOOP, loopBreakFlowStatement,
232          message("break_or_continue_outside_loop"));
233    }
234  }
235
236  @Override
237  public void visitMember(Member member) { }
238}