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);
+	}
+
+}