package nl.utwente.viskell.haskell.env;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import nl.utwente.viskell.haskell.type.*;
import nl.utwente.viskell.haskell.typeparser.TypeBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Haskell catalog containing available type classes and functions.
*/
public class HaskellCatalog {
private Map<String, DataTypeInfo> datatypes;
private Map<String, TypeClass> classes;
private Map<String, CatalogFunction> functions;
private Multimap<String, CatalogFunction> categories;
/** Default path to the XML file. */
public static final String XML_PATH = "/catalog/haskell.xml";
/** Default path to the XSD file. */
public static final String XSD_PATH = "/catalog/catalog.xsd";
/**
* Constructs a Haskell catalog using the given file location.
* @param path The path to the catalog XML file.
*/
public HaskellCatalog(final String path) {
this.functions = new HashMap<>();
this.categories = HashMultimap.create();
Document doc = getDocument(path, HaskellCatalog.XSD_PATH);
NodeList dataNodes = doc.getElementsByTagName("datatype");
NodeList classNodes = doc.getElementsByTagName("class");
NodeList functionNodes = doc.getElementsByTagName("function");
this.datatypes = this.parseDataType(dataNodes);
this.classes = this.parseClasses(classNodes);
Set<CatalogFunction> entries = this.parseFunctions(functionNodes, this.classes);
for (CatalogFunction entry : entries) {
this.functions.put(entry.getName(), entry);
this.categories.put(entry.getCategory(), entry);
}
}
/**
* Constructs a Haskell catalog using the default file location.
*/
public HaskellCatalog() {
this(HaskellCatalog.XML_PATH);
}
/**
* @return The set of category names.
*/
public final Set<String> getCategories() {
return this.categories.keySet();
}
/**
* @param key The name of the category.
* @return A set of the entries in the given category.
*/
public final Collection<CatalogFunction> getCategory(final String key) {
return this.categories.get(key);
}
/**
* @return A set of functions that match the given predicate.
*/
public final Collection<CatalogFunction> getByPredicate(final Predicate<CatalogFunction> predicate) {
return functions.values().stream().filter(predicate).collect(Collectors.toList());
}
/**
* @return A set of functions with names beginning with the given prefix.
*/
public final Collection<CatalogFunction> getByPrefix(final String prefix) {
return getByPredicate(fn -> fn.getName().startsWith(prefix));
}
/**
* @return A set of functions that match the given type.
*/
public final Collection<CatalogFunction> getByType(final Type type) {
return getByPredicate(fn -> {
try {
TypeChecker.unify("catalog query", fn.getFreshSignature(), type.getFresh());
} catch (HaskellTypeError e) {
return false;
}
return true;
});
}
/**
* @param name of the data type
* @return the haskell datatype if known, otherwise null
*/
public DataTypeInfo getDataType(String name) {
return this.datatypes.get(name);
}
/**
* @return The number of functions in the catalog.
*/
public int size() {
return functions.size();
}
/**
* @return A new environment based on the entries of this catalog.
*/
public final Environment asEnvironment() {
return new Environment(new HashMap<>(this.functions), new HashMap<>(this.classes));
}
/**
* Parses a list of class nodes into DataTypeInfo objects.
* @param nodes The nodes to parse.
* @return A mapping of DataTypeInfo objects for the given nodes.
*/
private Map<String, DataTypeInfo> parseDataType(NodeList nodes) {
Map<String, DataTypeInfo> entries = new HashMap<>();
TypeBuilder builder = new TypeBuilder(new HashMap<>());
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
String name = node.getAttributes().getNamedItem("name").getTextContent();
TypeCon typecon = TypeCon.con(name);
String arity = node.getAttributes().getNamedItem("typeArity").getTextContent();
int typeArity = Integer.parseInt(arity);
String prim = node.getAttributes().getNamedItem("builtin").getTextContent();
boolean builtin = Boolean.parseBoolean(prim);
DataTypeInfo datatype = new DataTypeInfo(typecon, typeArity, builtin);
NodeList cnodes = node.getChildNodes();
for (int j = 0; j < cnodes.getLength(); j++) {
Node inode = cnodes.item(j);
NamedNodeMap attrs = inode.getAttributes();
String cname = attrs.getNamedItem("name").getTextContent();
String signature = attrs.getNamedItem("signature").getTextContent();
Type ctype = builder.build(signature);
datatype.addConstructor(cname, ctype);
}
entries.put(name, datatype);
}
return entries;
}
/**
* Parses a list of class nodes into ClassEntry objects.
* @param nodes The nodes to parse.
* @return A set of ClassEntry objects for the given nodes.
*/
protected final Map<String, TypeClass> parseClasses(NodeList nodes) {
Map<String, TypeClass> entries = new HashMap<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
String name = node.getAttributes().getNamedItem("name").getTextContent();
TypeClass tc = new TypeClass(name);
TypeBuilder builder = new TypeBuilder(entries);
NodeList inodes = node.getChildNodes();
for (int j = 0; j < inodes.getLength(); j++) {
Node inode = inodes.item(j);
NamedNodeMap attrs = inode.getAttributes();
String inst = attrs.getNamedItem("name").getTextContent();
if ("instance".equals(inode.getNodeName())) {
String constrArgs = attrs.getNamedItem("constrainedArgs").getTextContent();
Type t = builder.build(inst);
if (t instanceof TypeCon) {
tc.addInstance((TypeCon) t, Integer.parseInt(constrArgs));
}
} else if ("superClass".equals(inode.getNodeName())) {
TypeClass sc = entries.get(inst);
if (sc == null) {
throw new RuntimeException("Can't resolve superclass " + inst + " of " + name);
} else {
tc.addSuperClass(sc);
}
}
}
Node def = node.getAttributes().getNamedItem("default");
if (def != null) {
Type dt = builder.build(def.getTextContent());
if (dt instanceof TypeCon) {
tc.setDefaultType((TypeCon)dt);
}
}
entries.put(name,tc);
}
return entries;
}
/**
* Parses a list of function nodes into FunctionEntry objects.
* @param nodes The nodes to parse.
* @return A set of FunctionEntry objects for the given nodes.
*/
protected final Set<CatalogFunction> parseFunctions(NodeList nodes, Map<String, TypeClass> typeClasses) {
Set<CatalogFunction> entries = new HashSet<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attributes = node.getAttributes();
String name = attributes.getNamedItem("name").getTextContent();
String signature = attributes.getNamedItem("signature").getTextContent();
Boolean isConstructor = attributes.getNamedItem("isConstructor") != null;
Boolean isCommon = attributes.getNamedItem("isCommon") != null;
String category = node.getParentNode().getAttributes().getNamedItem("name").getTextContent();
String documentation = node.getTextContent();
TypeBuilder builder = new TypeBuilder(typeClasses);
Type tsig = builder.build(signature);
entries.add(new CatalogFunction(name, category, tsig, documentation, isConstructor, isCommon));
}
return entries;
}
/**
* Loads the given XML catalog into a document.
* @param XMLPath The path to the XML file.
* @param XSDPath The path to the XSD file.
* @return The document for the XML file.
*/
protected static Document getDocument(final String XMLPath, final String XSDPath) {
URL xmlFile = HaskellCatalog.class.getResource(XMLPath);
URL schemaFile = HaskellCatalog.class.getResource(XSDPath);
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
SchemaFactory sFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
dbFactory.setIgnoringElementContentWhitespace(true);
dbFactory.setIgnoringComments(true);
dbFactory.setSchema(sFactory.newSchema(schemaFile));
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
return dBuilder.parse(xmlFile.openStream());
} catch (IOException | ParserConfigurationException | SAXException e) {
throw new RuntimeException("could not read or parse catalog file", e);
}
}
}