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

org.eclipse.xtext.xbase.compiler.ScopeStack Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.xbase.compiler;

import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Maps.*;
import static com.google.common.collect.Sets.*;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;


/**
 * @author Sven Efftinge - Initial contribution and API
 */
public class ScopeStack {
	
	private Stack  scopes = new Stack();
	
	public void openScope(boolean pseudoScope) {
		scopes.push(new Scope(pseudoScope));
	}
	
	public void closeScope() {
		scopes.pop();
	}
	
	/* @Nullable */
	public Object get(String name) {
		Variable var = internalGet(name);
		if (var != null) {
			return var.getReferenced();
		}
		return null;
	}
	private Variable internalGet(String name) {
		if (name == null)
			throw new NullPointerException("name");
		if (scopes.isEmpty())
			return null;
		
		int size = scopes.size();
		int i = size - 1;
		while (i >= 0) {
			Scope currentScope = scopes.get(i--);
			Variable var = currentScope.get(name);
			if (var != null) {
				return var;
			}
		}
		return null;
	}
	
	/* @Nullable */
	public String getName(Object referenced) {
		if (referenced == null)
			throw new NullPointerException("referenced");
		if (scopes.isEmpty())
			return null;
		
		int size = scopes.size();
		int i = size - 1;
		while (i >= 0) {
			Scope currentScope = scopes.get(i--);
			for (Variable v : currentScope.variables()) {
				if (v.referenced.equals(referenced))
					return v.name;
			}
		}
		return null;
	}
	
	/**
	 * @throws IllegalStateException if the referenced object does not have a name in the current scope.
	 */
	/* @NonNull */
	public String removeName(Object referenced) throws IllegalStateException {
		if (referenced == null)
			throw new NullPointerException("referenced");
		if (scopes.isEmpty())
			throw new IllegalStateException("Cannot find referenced object in current scope");
		Scope currentScope = scopes.get(scopes.size() - 1);
		Iterator iterator = currentScope.variables().iterator();
		while(iterator.hasNext()) {
			Variable v = iterator.next();
			if (v.referenced.equals(referenced)) {
				v.markRemoved();
				return v.name;
			}
		}
		throw new IllegalStateException("Cannot find referenced object in current scope");
	}
	
	public String declareVariable(/* @NonNull */ Object key, /* @NonNull */ String proposedName, boolean synthetic) {
		return declareVariable(key, proposedName, synthetic, false);
	}
	
	/**
	 * provides and registers a fresh variable in the current scope.
	 * It takes parent scopes into account and only reuses names of synthetic variables from parent scopes.
	 * Pseudo scopes are treated as if they were part of their parent scope.
	 * 
	 * @param withUniqueName
	 * 		This is a workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=445949
	 */
	/* @NonNull */
	public String declareVariable(/* @NonNull */ Object key, /* @NonNull */ String proposedName, boolean synthetic,
			boolean withUniqueName) {
		if (scopes.isEmpty())
			throw new IllegalArgumentException("No scope has been opened yet.");
		Scope currentScope = scopes.peek();
		if (internalGet(proposedName) == null) {
			currentScope.addVariable(proposedName, synthetic, key);
			return proposedName;
		}
		final Set names = newHashSet();
		boolean scopeClosed = false;
		// add only the non-synthetic variables, since they could be referenced from nested scopes.
		for (Scope scope : reverse(newArrayList(scopes))) {
			for (Variable variable : scope.variables()) {
				if (withUniqueName || !scopeClosed || !variable.synthetic)
					names.add(variable.name);
			}
			scopeClosed = scopeClosed || !scope.pseudoScope;
			// If we left the current scope (incl. pseudo scopes) and the variable is not synthetic or unique,
			// we can stop collecting names. Overriding names from outside is ok in that case. 
			if (scopeClosed && !(synthetic || withUniqueName))
				break;
		}
		String newName = findNewName(names, proposedName);
		currentScope.addVariable(newName, synthetic, key);
		return newName;
	}
	
	/* @NonNull */ 
	protected String findNewName(/* @NonNull */ Set names, /* @NonNull */ String proposedName) {
		if (names.contains(proposedName)) {
			for (int i = 1; i < Integer.MAX_VALUE; i++) {
				String newProposal = proposedName + "_" + i;
				if (!names.contains(newProposal))
					return newProposal;
			}
		}
		return proposedName;
	}

	static class Scope {
		public Scope(boolean pseudoScope) {
			this.pseudoScope = pseudoScope;
		}
		/**
		 * whether this scope is just a pseudo scope. 
		 * I.e. not backed up by a real Java scope.
		 */
		public boolean pseudoScope = false;
		
		private Map variables = newHashMap();
		
		public void addVariable(String name, boolean synthetic, Object element) {
			if (variables.containsKey(name))
				throw new IllegalArgumentException("variable '"+name+"' already declared in scope.");
			variables.put(name, new Variable(name, synthetic, element));
		}
		
		public Set variableNames() {
			return variables.keySet();
		}
		
		public Iterable variables() {
			return variables.values();
		}
		
		/* @Nullable */
		public Variable get(String name) {
			return variables.get(name);
		}
		
		@Override
		public String toString() {
			return (pseudoScope?"[PSEUDO]":"")+variables;
		}
	}
	
	final static class Variable {
		private static final Object REMOVED = new Object();
		
		Variable(String name2, boolean synthetic2, Object referenced) {
			this.name = name2;
			this.synthetic = synthetic2;
			this.referenced = referenced;
		}
		public String name;
		public boolean synthetic;
		public Object referenced;
		
		@Override
		public String toString() {
			if (referenced == REMOVED) {
				return "[removed]";
			}
			return referenced.getClass().getSimpleName();
		}
		private Object getReferenced() {
			if (referenced == REMOVED) {
				return null;
			}
			return referenced;
		}
		private void markRemoved() {
			this.referenced = REMOVED;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy