/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* 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 de.codesourcery.jasm16.compiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import de.codesourcery.jasm16.exceptions.DuplicateSymbolException;
import de.codesourcery.jasm16.parser.Identifier;
/**
* Default {@link ISymbolTable} implementation.
*
* @author tobias.gierke@code-sourcery.de
*/
public class SymbolTable implements ISymbolTable {
// key is symbol name
private final Map<Identifier,ISymbol> globalSymbols = new HashMap<>();
// key is fully-qualified scope name , value is map of local symbols associated with the scope
private final Map<String,Map<Identifier,ISymbol>> localSymbols = new HashMap<>();
private final String debugIdentifier;
private IParentSymbolTable parent;
public SymbolTable(String debugIdentifier) {
this.debugIdentifier = debugIdentifier;
}
@Override
public String dumpToString()
{
String result = "SymbolTable( "+debugIdentifier+"):\n\n";
result += "Global:\n";
for ( Entry<Identifier, ISymbol> e : globalSymbols.entrySet() ) {
result += ( e.getKey()+" => "+e.getValue() );
}
result += "\nLocal:\n";
for ( Entry<String, Map<Identifier, ISymbol>> e : localSymbols.entrySet() ) {
result += ( e.getKey()+" => "+e.getValue() );
}
return result;
}
@Override
public ISymbolTable createCopy()
{
final SymbolTable result = new SymbolTable(this.debugIdentifier);
// copy global symbols
for ( Entry<Identifier, ISymbol> entry : globalSymbols.entrySet() ) {
result.globalSymbols.put( entry.getKey() , entry.getValue().createCopy() );
}
// copy local symbols
for ( Entry<String, Map<Identifier, ISymbol>> entry : localSymbols.entrySet() )
{
final Map<Identifier,ISymbol> copy = new HashMap<>();
result.localSymbols.put( entry.getKey() , copy );
for ( Map.Entry<Identifier,ISymbol> entry2 : entry.getValue().entrySet() )
{
copy.put( entry2.getKey() , entry2.getValue().createCopy() );
}
}
return result;
}
@Override
public void defineSymbol( ISymbol symbol) throws DuplicateSymbolException
{
if (symbol == null) {
throw new IllegalArgumentException("symbol must not be NULL");
}
if ( DEBUG_SYMBOLS ) {
if ( symbol.isLocalSymbol() ) {
// new Exception("Defining LOCAL symbol "+symbol.getFullyQualifiedName()+" in table '"+this.debugIdentifier+"'").printStackTrace();
System.out.println("+++ Defining LOCAL symbol "+symbol.getFullyQualifiedName()+" in table '"+this.debugIdentifier+"'");
} else {
// new Exception("Defining GLOBAL symbol "+symbol.getFullyQualifiedName()+" in table '"+this.debugIdentifier+"'").printStackTrace();
System.out.println("+++ Defining GLOBAL symbol "+symbol.getFullyQualifiedName()+" in table '"+this.debugIdentifier+"'");
}
}
final ICompilationUnit unit = symbol.getCompilationUnit();
synchronized( unit )
{
final Identifier identifier = symbol.getName();
if ( symbol.isLocalSymbol() )
{
// local symbols must are always be scoped to a global one , assert it exists
final ISymbol globalSymbol = globalSymbols.get( symbol.getScope().getName() ); // TODO: Implicit assumption that symbols used as scope must always be top-level/global
if ( globalSymbol == null ) {
throw new IllegalArgumentException("Cannot define local symbol "+symbol+" without defining scope "+symbol.getScope()+" first");
}
if ( symbol.getScope() != globalSymbol ) {
throw new IllegalArgumentException("Local symbol needs to use the SAME global scope symbol instance contained in this ("+this+") symbol table");
}
final String fqName = symbol.getScope().getFullyQualifiedName();
Map<Identifier,ISymbol> locals = localSymbols.get( fqName );
if ( locals == null ) {
locals = new HashMap<>();
localSymbols.put( fqName , locals );
}
// check for duplicate local label
if ( locals.containsKey( symbol.getName() ) ) {
throw new DuplicateSymbolException( locals.get( symbol.getName() ) , symbol );
}
locals.put( symbol.getName() , symbol );
}
else
{
// define global symbol
final ISymbol existing = globalSymbols.get( identifier );
if ( existing != null )
{
throw new DuplicateSymbolException( existing , symbol );
}
globalSymbols.put( identifier, symbol );
}
}
}
public boolean containsSymbol(ISymbol s) {
if ( s.isGlobalSymbol() ) {
return globalSymbols.containsKey( s.getName().getRawValue() );
}
final Map<Identifier, ISymbol> existing = localSymbols.get( s.getFullyQualifiedName() );
return existing != null && existing.containsKey( s.getName() );
}
@Override
public ISymbol renameSymbol(ISymbol symbol, Identifier newIdentifier) throws DuplicateSymbolException
{
// TODO: Handle local symbols correctly
final ISymbol oldSymbol = getSymbol( symbol.getName() , symbol.getScope() );
if ( oldSymbol == null ) {
throw new IllegalArgumentException("Symbol "+symbol+" is not part of this symbol table?");
}
final ISymbol newSymbol = oldSymbol.withIdentifier( newIdentifier );
if ( getSymbol( newIdentifier , newSymbol.getScope() ) != null ) {
throw new DuplicateSymbolException( oldSymbol , newSymbol );
}
if ( symbol.isLocalSymbol() )
{
localSymbols.get( newSymbol.getScope().getFullyQualifiedName() ).put( newIdentifier , newSymbol );
}
else
{
globalSymbols.remove( symbol.getName() );
globalSymbols.put( newIdentifier , newSymbol );
// need to update all local symbols that were attached to the old symbol
final Map<Identifier, ISymbol> oldLocals = localSymbols.get( oldSymbol.getFullyQualifiedName() );
if ( oldLocals != null && ! oldLocals.isEmpty() )
{
final Map<Identifier, ISymbol> newLocals = new HashMap<>();
for ( Entry<Identifier, ISymbol> i : oldLocals.entrySet() ) {
newLocals.put( i.getKey() , i.getValue().withScope( newSymbol ) );
}
localSymbols.put( newSymbol.getFullyQualifiedName() , newLocals );
}
}
return newSymbol;
}
@Override
public ISymbol getSymbol(Identifier identifier, ISymbol scope)
{
if ( identifier == null ) {
throw new IllegalArgumentException("identifier must not be NULL");
}
if ( scope == null )
{
return globalSymbols.get( identifier );
}
// local symbol
final Map<Identifier, ISymbol> result = localSymbols.get( scope.getFullyQualifiedName() );
if ( result == null ) {
return null;
}
return result.get( identifier );
}
@Override
public boolean containsSymbol(Identifier identifier, ISymbol scope) {
return getSymbol( identifier , scope ) != null;
}
@Override
public String toString()
{
return "SymbolTable{"+debugIdentifier+"}";
}
@Override
public void clear() {
globalSymbols.clear();
localSymbols.clear();
}
@Override
public List<ISymbol> getSymbols()
{
final List<ISymbol> result = new ArrayList<ISymbol>( globalSymbols.values() );
for ( Entry<String, Map<Identifier, ISymbol>> locals : localSymbols.entrySet() ) {
result.addAll( locals.getValue().values() );
}
return result;
}
@Override
public IParentSymbolTable getParent()
{
return parent;
}
@Override
public void setParent(IParentSymbolTable table)
{
this.parent = table;
}
@Override
public int getSize()
{
int count = globalSymbols.size();
for ( Entry<String, Map<Identifier, ISymbol>> entry : localSymbols.entrySet() ) {
count += entry.getValue().size();
}
return count;
}
}