view 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 source

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

}