package me.august.lumen.compile.analyze;
import me.august.lumen.common.BytecodeUtil;
import me.august.lumen.compile.analyze.scope.*;
import me.august.lumen.compile.analyze.var.ClassVariable;
import me.august.lumen.compile.analyze.var.LocalVariable;
import me.august.lumen.compile.analyze.var.VariableReference;
import me.august.lumen.compile.codegen.BuildContext;
import me.august.lumen.compile.parser.ast.ClassNode;
import me.august.lumen.compile.parser.ast.FieldNode;
import me.august.lumen.compile.parser.ast.MethodNode;
import me.august.lumen.compile.parser.ast.Parameter;
import me.august.lumen.compile.parser.ast.expr.Expression;
import me.august.lumen.compile.parser.ast.expr.IdentExpr;
import me.august.lumen.compile.parser.ast.expr.OwnedExpr;
import me.august.lumen.compile.parser.ast.expr.RescueExpr;
import me.august.lumen.compile.parser.ast.stmt.*;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class VariableVisitor implements ASTVisitor {
private Scope scope;
private boolean skipNext;
// name-variable pairs that have yet to be added
// to a scope. Currently used for adding method
// parameters to the method body that follows.
Map<String, VariableReference> pending = new HashMap<>();
BuildContext build;
String className;
public VariableVisitor(BuildContext build) {
this.build = build;
}
@Override
public void visitClass(ClassNode cls) {
pushScope(new ClassScope(cls.getName()));
className = cls.getName();
}
@Override
public void visitField(FieldNode field) {
Type type = field.getResolvedType();
scope.setVariable(field.getName(), new ClassVariable(
className,
field.getName(),
type
));
}
@Override
public void visitMethod(MethodNode method) {
pushScope(new MethodScope(this.scope, method));
// local variable index
int idx = method.isStatic() ? -1 : 0;
for (Parameter param : method.getParameters()) {
idx++;
Type type = param.getResolvedType();
scope.setVariable(param.getName(), new LocalVariable(idx, type));
}
// when the method body is visited, skip it.
// we already created the scope for the body here.
skipNext();
}
@Override
public void visitBody(Body body) {
pushScope(new Scope(this.scope));
// wrap keyset in new ArrayList to prevent
// ConcurrentModificationException
for (String name : new ArrayList<>(pending.keySet())) {
scope.setVariable(name, pending.remove(name));
}
}
@Override
public void visitBodyEnd(Body body) {
popScope();
}
@Override
public void visitClassEnd(ClassNode cls) {
popScope();
}
@Override
public void visitWhileStmt(WhileStmt stmt) {
pushScope(new LoopScope(scope, stmt));
}
@Override
public void visitEachStmt(EachStmt stmt) {
Scope scope = pushScope(new LoopScope(this.scope, stmt));
Type componentType = BytecodeUtil.componentType(stmt.getExpr().expressionType());
int counterIndex = nextLocalIndex();
stmt.setCounterIndex(counterIndex);
scope.setVariable(null, new LocalVariable(counterIndex, Type.INT_TYPE));
int arrayIndex = nextLocalIndex();
stmt.setArrayIndex(arrayIndex);
scope.setVariable(null, new LocalVariable(arrayIndex, stmt.getExpr().expressionType()));
int targetIndex = nextLocalIndex();
scope.setVariable(
stmt.getIdent(),
new LocalVariable(targetIndex, componentType)
);
stmt.setTargetIndex(targetIndex);
}
@Override
public void visitForStmt(ForStmt stmt) {
Scope scope = pushScope(new LoopScope(this.scope, stmt));
int counterIndex = nextLocalIndex();
stmt.setCounterIndex(counterIndex);
scope.setVariable(stmt.getIdent(), new LocalVariable(counterIndex, Type.INT_TYPE));
int boundIndex = nextLocalIndex();
stmt.setBoundIndex(boundIndex);
scope.setVariable(null, new LocalVariable(counterIndex, Type.INT_TYPE));
}
@Override
public void visitBreakStmt(BreakStmt stmt) {
LoopScope loopScope = nextLoop();
if (loopScope == null) {
build.error(
"`break` used where loop not found",
false, stmt
);
}
stmt.setOwner(loopScope.getLoop());
}
@Override
public void visitNextStmt(NextStmt stmt) {
LoopScope loopScope = nextLoop();
build.error(
"`next` used where loop not found",
false, stmt
);
stmt.setOwner(loopScope.getLoop());
}
@Override
public void visitVar(VarStmt var) {
Type type = var.getResolvedType();
LocalVariable ref = new LocalVariable(nextLocalIndex(), type);
scope.setVariable(var.getName(), ref);
var.setRef(ref);
}
@Override
public void visitExpression(Expression expr) {
if (expr instanceof OwnedExpr) {
Expression tail = ((OwnedExpr) expr).getTail();
if (tail instanceof IdentExpr) {
handleIdent((IdentExpr) tail);
}
} else if (expr instanceof RescueExpr) {
((RescueExpr) expr).setExceptionVariableIndex(nextLocalIndex());
}
}
private void handleIdent(IdentExpr expr) {
VariableReference var = scope.getVariable(expr.getIdentifier());
if (var == null) {
build.error(
"Undefined variable: " + expr.getIdentifier(),
false, expr
);
}
expr.setVariableReference(var);
expr.setExpressionType(var.getType());
}
/**
* Gets the next local variable index starting
* at a specific Scope
* @return The next local variable index
*/
public int nextLocalIndex() {
Scope s = this.scope;
int max = -1;
while (s != null) {
for (VariableReference vr : s.getVariableTable().values()) {
if (vr instanceof LocalVariable) {
LocalVariable local = (LocalVariable) vr;
if (local.getIndex() > max)
max = local.getIndex();
}
}
s = s.getParent();
}
if (max == -1) {
MethodScope methodScope = (MethodScope) scope.fromType(ScopeType.METHOD);
return methodScope.getMethod().isStatic() ? 0 : 1;
} else {
return max + 1;
}
}
private void skipNext() {
this.skipNext = true;
}
private Scope pushScope(Scope scope) {
if (skipNext) {
skipNext = false;
return null;
} else {
if (scope.getParent() == null)
scope.setParent(this.scope);
return this.scope = scope;
}
}
private void popScope() {
if (scope.getParent() == null) {
scope = null;
} else {
scope = scope.getParent();
}
}
private LoopScope nextLoop() {
return (LoopScope) scope.fromType(ScopeType.LOOP);
}
public Scope getScope() {
return scope;
}
public void setScope(Scope scope) {
this.scope = scope;
}
}