All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sandius.rembulan.parser.analysis.LabelResolutionTransformer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Miroslav Janíček
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sandius.rembulan.parser.analysis;

import net.sandius.rembulan.parser.ast.*;
import net.sandius.rembulan.util.Check;

import java.util.*;

class LabelResolutionTransformer extends Transformer {

	private final Deque scopes;

	private final Map defs;
	private final Map uses;

	public LabelResolutionTransformer() {
		this.scopes = new ArrayDeque<>();
		this.defs = new HashMap<>();
		this.uses = new HashMap<>();
	}

	private static boolean isVoidStatement(Statement stat) {
		return stat instanceof LabelStatement || stat instanceof GotoStatement;
	}

	private void enterBlock() {
		scopes.push(new Scope());
	}

	private void endBlockScopes() {
		scopes.peek().clearLocals();
	}

	private void leaveBlock() {
		Scope s = scopes.pop();

		if (!scopes.isEmpty()) {
			Scope current = scopes.peek();
			current.addGotos(s.pending);
		}
		else {
			// this was the top block
			if (!s.pending.isEmpty()) {
				GotoStatement gs = s.pending.get(0).statement;
				throw new NameResolutionException("no visible label '" + gs.labelName().value() + "' for  at line " + gs.line());
			}
		}
	}

	private void defLabel(LabelStatement node) {
		scopes.peek().defLabel(node);
	}

	private void useLabel(GotoStatement node) {
		scopes.peek().useLabel(node);
	}

	private void defLocal(Name name) {
		scopes.peek().defLocal(name);
	}

	private Block annotate(Block b) {
		LabelAnnotatorTransformer annotator = new LabelAnnotatorTransformer() {

			@Override
			protected Object annotation(LabelStatement node) {
				ResolvedLabel rl = defs.get(node);
				if (rl == null) {
					throw new IllegalStateException("unresolved label '" + node.labelName().value() + "' in label statement at line " + node.line());
				}
				return rl;
			}

			@Override
			protected Object annotation(GotoStatement node) {
				ResolvedLabel rl = uses.get(node);
				if (rl == null) {
					throw new IllegalStateException("unresolved label '" + node.labelName().value() + "' in goto statement at line " + node.line());
				}
				return rl;
			}

		};

		return annotator.transform(b);
	}

	private Block transformTopBlock(Block b) {
		b = transform(b);
		return annotate(b);
	}

	@Override
	public Chunk transform(Chunk chunk) {
		return chunk.update(this.transformTopBlock(chunk.block()));
	}

	@Override
	public Expr transform(FunctionDefExpr e) {
		LabelResolutionTransformer child = new LabelResolutionTransformer();
		return e.update(e.params(), child.transformTopBlock(e.block()));
	}

	private static Statement lastNonVoidStatement(Block block) {
		Statement result = null;
		for (BodyStatement bs : block.statements()) {
			if (!isVoidStatement(bs)) {
				result = bs;
			}
		}
		if (block.returnStatement() != null) {
			result = block.returnStatement();
		}

		return result;
	}

	@Override
	public Block transform(Block block) {
		enterBlock();

		Statement localScopeEnd = lastNonVoidStatement(block);

		List stats = new ArrayList<>();
		for (BodyStatement bs : block.statements()) {
			stats.add(bs.accept(this));

			if (Objects.equals(localScopeEnd, bs)) {
				endBlockScopes();
			}
		}

		ReturnStatement ret = block.returnStatement() != null
				? block.returnStatement().accept(this)
				: null;

		leaveBlock();

		return block.update(Collections.unmodifiableList(stats), ret);
	}

	@Override
	public BodyStatement transform(LabelStatement node) {
		defLabel(node);
		return node;
	}

	@Override
	public BodyStatement transform(GotoStatement node) {
		useLabel(node);
		return node;
	}

	@Override
	public BodyStatement transform(LocalDeclStatement node) {
		defLocal(node.names().get(0));
		return node;
	}

	private static class LabelDef {

		public final int line;
		public final ResolvedLabel labelObject;

		private LabelDef(int line, ResolvedLabel labelObject) {
			this.line = line;
			this.labelObject = Check.notNull(labelObject);
		}

	}

	private static class PendingGoto {

		private final GotoStatement statement;
		private final List localsSince;

		private PendingGoto(GotoStatement statement) {
			this.statement = Check.notNull(statement);
			this.localsSince = new ArrayList<>();
		}

		public void addLocal(Name local) {
			localsSince.add(local);
		}

	}

	private class Scope {

		private final Map definedHere;
		private final List pending;
		private final Set locals;

		public Scope() {
			this.definedHere = new HashMap<>();
			this.pending = new ArrayList<>();
			this.locals = new HashSet<>();
		}

		public void defLabel(LabelStatement node) {
			Name labelName = node.labelName();

			ResolvedLabel rl = new ResolvedLabel();

			{
				LabelDef prevDef = definedHere.put(labelName, new LabelDef(node.line(), rl));
				if (prevDef != null) {
					throw new NameResolutionException("label '" + labelName.value() + "' already defined on line " + prevDef.line);
				}
			}

			{
				ResolvedLabel old = defs.put(node, rl);
				assert (old == null);
			}

			List resolvedGotos = new ArrayList<>();
			for (PendingGoto pg : pending) {
				GotoStatement gotoStat = pg.statement;

				if (gotoStat.labelName().equals(labelName)) {
					if (!pg.localsSince.isEmpty()) {
						Name localName = pg.localsSince.get(0);
						throw new NameResolutionException(" at line " + gotoStat.line() + " jumps into the scope of local '" + localName.value() + "'");
					}
					else {
						ResolvedLabel old = uses.put(gotoStat, rl);
						assert (old == null);
						resolvedGotos.add(pg);  // mark for removal from the pending set
					}
				}
			}

			pending.removeAll(resolvedGotos);
		}

		public void useLabel(GotoStatement node) {
			LabelDef ldef = definedHere.get(node.labelName());

			if (ldef != null) {
				ResolvedLabel old = uses.put(node, ldef.labelObject);
				assert (old == null);
			}
			else {
				pending.add(new PendingGoto(node));
			}
		}

		public void addGotos(Iterable gotos) {
			for (PendingGoto pg : gotos) {
				useLabel(pg.statement);
			}
		}

		public void defLocal(Name name) {
			locals.add(name);
			for (PendingGoto pg : pending) {
				pg.addLocal(name);
			}
		}

		public void clearLocals() {
			for (Name n : locals) {
				for (PendingGoto pg : pending) {
					pg.localsSince.remove(n);
				}
			}
			locals.clear();
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy