Mercurial Hosting > nabble
diff src/nabble/naml/compiler/Program.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/nabble/naml/compiler/Program.java Thu Mar 21 19:15:52 2019 -0600 @@ -0,0 +1,403 @@ +package nabble.naml.compiler; + +import fschmidt.util.java.Computable; +import fschmidt.util.java.ComputationException; +import fschmidt.util.java.FutureValue; +import fschmidt.util.java.Identity; +import fschmidt.util.java.Interner; +import fschmidt.util.java.Memoizer; +import fschmidt.util.java.ObjectUtils; +import nabble.naml.dom.ElementName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + + +public final class Program { + private static final Logger logger = LoggerFactory.getLogger(Program.class); + + private static final class Key { + final String name; + final GenericNamespace[] base; + + Key(String name,GenericNamespace[] base) { + if( name==null ) + throw new NullPointerException("name is null"); + this.name = name; + this.base = base; + } + + public boolean equals(Object obj) { + if( !(obj instanceof Key) ) + return false; + Key key = (Key)obj; + return key.name.equals(name) && Arrays.equals(key.base,base); + } + + public int hashCode() { + return name.hashCode(); + } + } + + private static final class PartUsage { + private final Identity<ElementName.Part> part; + private final Usage usage; + + PartUsage(ElementName.Part part,Usage usage) { + this.part = part.identity(); + this.usage = usage; + } + + public boolean equals(Object obj) { + if( !(obj instanceof PartUsage) ) + return false; + PartUsage p = (PartUsage)obj; + return p.part==part && ObjectUtils.equals(p.usage,usage); + } + + public int hashCode() { + int hash = part.hashCode(); + if( usage != null ) + hash += usage.hashCode(); + return hash; + } + } + + private final List<Source> sources; + private final Collection<Module> modules; + private final ConcurrentMap<PartUsage,Meaning> partMeaning = new ConcurrentHashMap<PartUsage,Meaning>(); + private final ConcurrentMap<String,Meaning> meaningMap = new ConcurrentHashMap<String,Meaning>(); + private final ConcurrentMap<Meaning,Set<Usage>> usageMap = new ConcurrentHashMap<Meaning,Set<Usage>>(); + + private final Memoizer<Key,Template> macroTemplates = new Memoizer<Key,Template>(new Computable<Key,Template>() { + public Template get(Key key) throws ComputationException { + try { + long start = System.currentTimeMillis(); + Template t = Compiler.compile(Program.this,key.name,key.base); + logger.info("Compiling '"+key.name+"' took " + (System.currentTimeMillis()-start) + " ms"); + return t; + } catch(CompileException e) { + throw new ComputationException(e); + } + } + }); + + private final FutureValue<Set<String>> moduleNames = new FutureValue<Set<String>>() { + protected Set<String> compute() { + Set<String> moduleNames = new HashSet<String>(); + for( Module m : modules ) { + if( !moduleNames.add(m.getName()) ) + throw new RuntimeException("duplicate module: "+m.getName()); + } + return moduleNames; + } + }; + + private final FutureValue<Map<Class,List<JavaNamespace>>> extensionMap = new FutureValue<Map<Class,List<JavaNamespace>>>() { + protected Map<Class,List<JavaNamespace>> compute() { + Map<Class,List<JavaNamespace>> extensionMap = new HashMap<Class,List<JavaNamespace>>(); + for( Module m : modules ) { + for( Class extensionClass : m.getExtensions() ) { + JavaNamespace ns = JavaNamespace.getNamespaceExt(extensionClass); + List<JavaNamespace> extensions = extensionMap.get(ns.extensionTarget); + if( extensions == null ) { + extensions = new ArrayList<JavaNamespace>(); + extensionMap.put(ns.extensionTarget,extensions); + } + extensions.add(ns); + } + } + return extensionMap; + } + }; + + private final FutureValue<Set<String>> staticNames = new FutureValue<Set<String>>() { + protected Set<String> compute() throws CompileException { + Set<String> staticNames = new HashSet<String>(); + for( Source source : getSources() ) { + staticNames.addAll(source.staticNames()); + } + return staticNames; + } + }; + + private static class Macros { + final Map<String,List<Macro>> nameMap = new HashMap<String,List<Macro>>(); + final Map<Macro,Macro> overrideMap = new HashMap<Macro,Macro>(); + final Map<Macro,Macro> overriddenMap = new HashMap<Macro,Macro>(); + } + + private static final class MacroKey { + private final Macro macro; + + MacroKey(Macro macro) { + this.macro = macro; + } + + @Override public boolean equals(Object obj) { + if( !(obj instanceof MacroKey) ) + return false; + MacroKey key = (MacroKey)obj; + return key.macro.name.equals(macro.name) && key.macro.requiredNamespaces.equals(macro.requiredNamespaces); + } + + @Override public int hashCode() { + return macro.name.hashCode() + 31 * macro.requiredNamespaces.hashCode(); + } + } + + private final FutureValue<Macros> macros = new FutureValue<Macros>() { + protected Macros compute() throws CompileException { + Macros macros = new Macros(); + Map<MacroKey,Macro> map = new HashMap<MacroKey,Macro>(); + StackTrace stackTrace = new StackTrace(); + for( Source source : getSources() ) { + for( Macro macro : source.getMacros() ) { + stackTrace.push( new StackTraceElement(macro) ); + try { + Macro dup = map.put(new MacroKey(macro),macro); + if( !macro.isOverride ) { + if( dup != null ) + throw new CompileException(stackTrace,"macro '"+macro+"' conflicts with '"+dup+"'"); + } else { + if( dup == null ) + throw new CompileException(stackTrace,"no macro found to override"); + macros.overrideMap.put(macro,dup); + macros.overriddenMap.put(dup,macro); + } + addMeaning(macro); + } finally { + stackTrace.pop(); + } + } + } + for( Macro macro : map.values() ) { + List<Macro> list = macros.nameMap.get(macro.name); + if( list == null ) { + list = Collections.singletonList(macro); + macros.nameMap.put(macro.name,list); + } else if( list.size() == 1 ) { + list = new ArrayList<Macro>(list); + list.add(macro); + macros.nameMap.put(macro.name,list); + } else { + list.add(macro); + } + } + return macros; + } + }; + + private Program(List<Module> modules) { + this.modules = modules; + this.sources = new ArrayList<Source>(); + Set<String> names = new HashSet<String>(); + for( Module module : modules ) { + for( String dependency : module.getDependencies() ) { + if( !names.contains(dependency) ) + throw new IllegalArgumentException("module '"+module.getName()+"' dependency '"+dependency+"' not found"); + } + if( !names.add(module.getName()) ) + throw new IllegalArgumentException("duplicate module name: "+module.getName()); + this.sources.addAll( module.getSources() ); + } + + // check sources + Set<String> ids = new HashSet<String>(); + for( Source s : this.sources ) { + if( !ids.add(s.id) ) + throw new IllegalArgumentException("duplicate source: "+s); + } + } + + Set<String> moduleNames() { + return moduleNames.get(); + } + + Map<Class,List<JavaNamespace>> extensionMap() { + return extensionMap.get(); + } + + Set<String> staticNames() { + return staticNames.get(); + } + + Map<String,List<Macro>> macros() { + return macros.get().nameMap; + } + + Map<Macro,Macro> overrides() { + return macros.get().overrideMap; + } + + Map<Macro,Macro> overridden() { + return macros.get().overriddenMap; + } + + public List<Source> getSources() { + return sources; + } + + public Template getTemplate(String templateName) + throws CompileException + { + return getTemplate(templateName,new GenericNamespace[0]); + } + + public Template getTemplate(String templateName,String... base) + throws CompileException + { + return getTemplate(templateName,getNamespaces(base)); + } + + public Template getTemplate(String templateName,Class... base) + throws CompileException + { + GenericNamespace[] nsBase = new GenericNamespace[base.length]; + for( int i=0; i<base.length; i++ ) { + nsBase[i] = JavaNamespace.getNamespace(base[i]); + } + return getTemplate(templateName,nsBase); + } + + Template getTemplate(String templateName,GenericNamespace... base) + throws CompileException + { + try { + Key key = new Key(templateName,base); + return macroTemplates.get(key); + } catch(ComputationException e) { + Throwable cause = e.getCause(); + if( cause instanceof CompileException ) + throw (CompileException)cause; + throw e; + } + } + + public boolean equals(Object obj) { + if( !(obj instanceof Program) ) + return false; + Program t = (Program)obj; + return t.sources.equals(sources) && t.modules.equals(modules); + } + + public int hashCode() { + return sources.hashCode() + 31*modules.hashCode(); + } + + void addMeaning(Meaning meaning) { + Meaning old = meaningMap.putIfAbsent( meaning.getId(), meaning ); + if( old != null && old != meaning ) { + throw new RuntimeException("meaning="+meaning+" old="+old); + } + } + + void addMeaning(ElementName.Part part,Meaning meaning,MacroScope macroScope,GenericNamespace[] base) { + Set<Usage> usages = usageMap.get(meaning); + if( usages == null ) { + usages = Collections.newSetFromMap( new ConcurrentHashMap<Usage,Boolean>() ); + Set<Usage> old = usageMap.putIfAbsent(meaning,usages); + if( old != null ) + usages = old; + } + if( part != null ) { + List<Macro> macroPath = new ArrayList<Macro>(); + addMacros(macroScope,macroPath); + Usage usage = new Usage(base,macroPath).intern(); + usages.add(usage); + PartUsage p = new PartUsage(part,null); + Meaning m = partMeaning.putIfAbsent(p,meaning); + if( m!=null && !m.equals(meaning) ) { + p = new PartUsage(part,usage); + m = partMeaning.putIfAbsent(p,meaning); + if( m!=null && !m.equals(meaning) ) + throw new RuntimeException(); + } + } + } + + private void addMacros(MacroScope macroScope,List<Macro> macroPath) { + if( macroScope.parentScope != null ) + addMacros(macroScope.parentScope,macroPath); + macroPath.add(macroScope.macro); + } + + public final Meaning getMeaning(ElementName.Part namePart,Usage usage) { + Meaning m = partMeaning.get(new PartUsage(namePart,usage)); + if( m == null ) + m = partMeaning.get(new PartUsage(namePart,null)); + return m; + } + + public final Meaning getMeaning(String id) { + return meaningMap.get(id); + } + + public Macro getMacroOverriddenBy(Macro macro) { + return overrides().get(macro); + } + + public Macro getMacroWhichOverrides(Macro macro) { + return overridden().get(macro); + } + + public boolean isCompiled(Meaning meaning) { + return usageMap.containsKey(meaning); + } + + public Set<Usage> getUsages(Meaning meaning) { + return usageMap.get(meaning); + } + + private static final Interner<Program> interner = new Interner<Program>(); + + public static Program getInstance(List<Module> modules) { + return interner.intern(new Program(modules)); + } + + public Usage getUsage(String[] baseIds,List<Macro> macroPath) { + return new Usage(getNamespaces(baseIds),macroPath); + } + + private GenericNamespace[] getNamespaces(String[] ids) { + GenericNamespace[] a = new GenericNamespace[ids.length]; + for( int i=0; i<ids.length; i++ ) { + a[i] = getNamespace(ids[i]); + } + return a; + } + + private GenericNamespace getNamespace(String id) { + if( id.indexOf('!') == -1 ) { + try { + return JavaNamespace.getNamespace(Class.forName(id)); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + } else { + Macro macro = (Macro)getMeaning(id); + try { + return new MacroNamespace(macro,new StackTrace(),macros()); + } catch(CompileException e) { + throw new RuntimeException(e); + } + } + } + + public Collection<Macro> getMacrosByName(String name) { + return macros().get(name); + } + +}