//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 26 Dec 05 Brian Frank Creation
// 5 Jun 06 Brian Frank Ported from Java to Fan
//
**
** ResolveImports maps every Using node in each CompilationUnit to a pod
** and ensures that it exists and that no imports are duplicated. Then we
** create a map for all the types which are imported into the CompilationUnit
** so that the Parser can quickly distinguish between a type identifier and
** other identifiers. The results of this step populate Using.resolvedXXX and
** CompilationUnit.importedTypes.
**
class ResolveImports : CompilerStep
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Constructor takes the associated Compiler
**
new make(Compiler compiler)
: super(compiler)
{
resolved[pod.name] = pod
}
//////////////////////////////////////////////////////////////////////////
// Methods
//////////////////////////////////////////////////////////////////////////
**
** Run the step
**
override Void run()
{
log.debug("ResolveImports")
// process each unit for Import.pod
units.each |CompilationUnit unit|
{
try
resolveImports(unit)
catch (CompilerErr e)
errReport(e)
}
bombIfErr
// process each unit for CompilationUnit.importedTypes
units.each |CompilationUnit unit|
{
try
resolveImportedTypes(unit)
catch (CompilerErr e)
errReport(e)
}
bombIfErr
// if this is a script, make our using imports the depends
if (compiler.input.isScript)
{
resolved.each |pod|
{
if (pod !== compiler.pod && pod.name != "sys")
compiler.depends.add(CDepend.fromStr("$pod.name $pod.version"))
}
}
}
**
** Resolve all the imports in the specified unit
** and ensure there are no duplicates.
**
private Void resolveImports(CompilationUnit unit)
{
// map to keep track of duplicate imports
// within this compilation unit
dups := Str:Using[:]
// process each import statement (remember the
// first one is an implicit import of sys)
unit.usings.each |Using u|
{
podName := u.podName
// check that this podName was in the compiler's
// input dependencies
checkUsingPod(this, podName, u.loc)
// don't allow a using my own pod
if (u.typeName == null && u.podName == compiler.pod.name)
err("Using '$u.podName' is on pod being compiled", u.loc)
// check for duplicate imports
key := podName
if (u.typeName != null) key += "::$u.typeName"
if (dups.containsKey(key))
{
err("Duplicate using '$key'", u.loc)
return
}
dups[key] = u
// if already resolved, then just use it
u.resolvedPod = resolved[podName]
// resolve the import and cache in resolved map
if (u.resolvedPod == null)
{
try
{
pod := ns.resolvePod(podName, u.loc)
resolved[podName] = u.resolvedPod = pod
}
catch (CompilerErr e)
{
errReport(e)
return
}
}
// if type specified, then resolve type
if (u.typeName != null)
{
u.resolvedType = u.resolvedPod.resolveType(u.typeName, false)
if (u.resolvedType== null)
{
err("Type not found in pod '$podName::$u.typeName'", u.loc)
return
}
}
}
}
**
** Create a unified map of type names to CType[] for all the
** imports in the specified unit (this includes types within
** the pod being compilied itself). For example if foo::Thing
** and bar::Thing are imported, then importedTypes would contain
** "Thing" : [foo::Thing, bar::Thing]
**
private Void resolveImportedTypes(CompilationUnit unit)
{
// name -> CType[]
types := Str:CType[][:]
// add types for my own pod
addAll(types, compiler.pod.types)
// add pod level imports first
unit.usings.each |Using u|
{
if (u.typeName == null)
addAll(types, u.resolvedPod.types)
}
// add type specific imports last (these
// override any pod level imports)
unit.usings.each |Using u|
{
if (u.typeName != null)
{
if (u.asName == null)
{
types[u.typeName] = [u.resolvedType]
}
else
{
remove(types, u.resolvedType)
types[u.asName] = [u.resolvedType]
}
}
}
/*
// dump
echo("--- types for $unit")
ids := types.keys.sort
ids.each |Str id| { echo("$id = ${types[id]}") }
*/
// save away on unit
unit.importedTypes = types
}
private Void addAll(Str:CType[] types, CType[] toAdd)
{
toAdd.each |CType t|
{
list := types[t.name]
if (list == null) types[t.name] = list = CType[,]
list.add(t)
}
}
private Void remove(Str:CType[] types, CType t)
{
list := types[t.name]
if (list != null)
{
for (i:=0; i<list.size; ++i)
if (list[i].qname == t.qname) { list.removeAt(i); break }
}
}
//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////
**
** Resolve a fully qualified type name into its CType representation.
** This may be a TypeDef within the compilation units or could be
** an imported type. If the type name cannot be resolved then we
** log an error and return null.
**
static CType? resolveQualified(CompilerSupport cs, Str podName, Str typeName, Loc loc)
{
// first check pod being compiled
if (podName == cs.compiler.pod.name)
{
t := cs.pod.resolveType(typeName, false)
if (t == null)
{
cs.err("Type '$typeName' not found within pod being compiled", loc)
return null
}
return t
}
// resolve pod
pod := resolvePod(cs, podName, loc)
if (pod == null) return null
// now try to lookup type
t := pod.resolveType(typeName, false)
if (t == null)
{
cs.err("Type '$typeName' not found in pod '$podName'", loc);
return null
}
return t
}
**
** Resolve a pod name into its CPod representation. If pod
** cannot be resolved then log an error and return null.
**
static CPod? resolvePod(CompilerSupport cs, Str podName, Loc loc)
{
// if this is the pod being compiled no further checks needed
if (cs.pod.name == podName) return cs.pod
// otherwise we need to try to resolve pod
CPod? pod := null
try
{
pod = cs.ns.resolvePod(podName, loc)
}
catch (CompilerErr e)
{
cs.errReport(e)
return null
}
// check that we have a dependency on the pod
checkUsingPod(cs, podName, loc)
return pod
}
**
** Check that a pod name is in the dependency list.
**
private static Void checkUsingPod(CompilerSupport cs, Str podName, Loc loc)
{
// scripts don't need dependencies
if (cs.compiler.input.isScript) return
// if we have a declared dependency that is ok
if (cs.ns.depends.containsKey(podName)) return
// if this is the pod being compiled that is obviously ok
if (cs.pod.name == podName) return
// we don't require explicit dependencies on FFI
if (podName.startsWith("[")) return
// we got a problem
cs.err("Using '$podName' which is not a declared dependency for '$cs.pod.name'", loc)
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
Str:CPod resolved := Str:CPod[:] // reuse CPods across units
}