--- /dev/null
+package org.simantics.scl.compiler.markdown.html;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.procedure.TObjectProcedure;
+import gnu.trove.set.hash.THashSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import org.simantics.scl.compiler.common.datatypes.Constructor;
+import org.simantics.scl.compiler.elaboration.modules.SCLValue;
+import org.simantics.scl.compiler.elaboration.modules.TypeClass;
+import org.simantics.scl.compiler.elaboration.modules.TypeConstructor;
+import org.simantics.scl.compiler.environment.filter.AcceptAllNamespaceFilter;
+import org.simantics.scl.compiler.errors.Failable;
+import org.simantics.scl.compiler.markdown.internal.ExtensionNodeHandler;
+import org.simantics.scl.compiler.markdown.internal.HtmlEscape;
+import org.simantics.scl.compiler.markdown.internal.MarkdownParser;
+import org.simantics.scl.compiler.markdown.nodes.CodeBlockNode;
+import org.simantics.scl.compiler.markdown.nodes.DocumentNode;
+import org.simantics.scl.compiler.markdown.nodes.HtmlNode;
+import org.simantics.scl.compiler.markdown.nodes.Node;
+import org.simantics.scl.compiler.module.Module;
+import org.simantics.scl.compiler.module.repository.ModuleRepository;
+import org.simantics.scl.compiler.types.TPred;
+import org.simantics.scl.compiler.types.TVar;
+import org.simantics.scl.compiler.types.Types;
+import org.simantics.scl.compiler.types.util.TypeUnparsingContext;
+
+public class SCLDocumentationExtensionNodeHandler implements ExtensionNodeHandler {
+
+ final ModuleRepository moduleRepository;
+ final String documentationName;
+
+ Failable<Module> relatedModule;
+ THashMap<String,Failable<Module>> moduleCache = new THashMap<String,Failable<Module>>();
+
+ THashSet<String> documentedValues = new THashSet<String>();
+ THashSet<String> documentedClasses = new THashSet<String>();
+ THashSet<String> documentedTypeConstructors = new THashSet<String>();
+
+ public SCLDocumentationExtensionNodeHandler(ModuleRepository moduleRepository,
+ String documentationName) {
+ this.moduleRepository = moduleRepository;
+ this.documentationName = documentationName;
+ }
+
+ @Override
+ public DocumentNode expandBlock(String extension, String content) {
+ if(extension.equals("value")) {
+ String[] names = content.split(",");
+ DocumentNode container = new DocumentNode();
+ for(String name : names) {
+ name = name.trim();
+ if(name.isEmpty())
+ continue;
+ if(!documentedValues.add(name))
+ System.err.println("Value '" + name + "' has already been documented in " + documentationName + ".");
+ generateValueDocumentation(container, name);
+ }
+ return container;
+ }
+ else if(extension.equals("class")) {
+ String[] names = content.split(",");
+ DocumentNode container = new DocumentNode();
+ for(String name : names) {
+ name = name.trim();
+ if(name.isEmpty())
+ continue;
+ if(!documentedClasses.add(name))
+ System.err.println("Class '" + name + "' has already been documented in " + documentationName + ".");
+ generateClassDocumentation(container, name);
+ }
+ return container;
+ }
+ else if(extension.equals("data")) {
+ String[] names = content.split(",");
+ DocumentNode container = new DocumentNode();
+ for(String name : names) {
+ name = name.trim();
+ if(name.isEmpty())
+ continue;
+ if(!documentedTypeConstructors.add(name))
+ System.err.println("Type constructor '" + name + "' has already been documented in " + documentationName + ".");
+ generateDataDocumentation(container, name);
+ }
+ return container;
+ }
+ else if(extension.equals("undocumented")) {
+ Module module = getRelatedModule();
+ if(module == null)
+ return null;
+ final ArrayList<String> undocumentedValues = new ArrayList<String>();
+ module.findValuesForPrefix("", AcceptAllNamespaceFilter.INSTANCE, new TObjectProcedure<SCLValue>() {
+ @Override
+ public boolean execute(SCLValue value) {
+ if(value.isPrivate())
+ return true;
+ String name = value.getName().name;
+ if(documentedValues.contains(name) ||
+ name.charAt(0) == '_' ||
+ (name.contains("$") && Character.isAlphabetic(name.charAt(0))))
+ return true;
+ undocumentedValues.add(name);
+ return true;
+ }
+ });
+ Collections.sort(undocumentedValues);
+
+ DocumentNode container = new DocumentNode();
+ for(String name : undocumentedValues) {
+ generateValueDocumentation(container, name);
+ }
+ return container;
+ }
+ else
+ return null;
+ }
+
+ private Module getRelatedModule() {
+ if(relatedModule == null) {
+ relatedModule = moduleRepository.getModule(documentationName);
+ if(!relatedModule.didSucceed())
+ System.err.println("Couldn't load the module " + documentationName);
+ }
+ if(relatedModule.didSucceed())
+ return relatedModule.getResult();
+ else
+ return null;
+ }
+
+ private Module getModule(String moduleName) {
+ Failable<Module> fm = moduleCache.get(moduleName);
+ if(fm == null) {
+ fm = moduleRepository.getModule(moduleName);
+ moduleCache.put(moduleName, fm);
+ if(!fm.didSucceed())
+ System.err.println("Couldn't load the module " + moduleName);
+ }
+ if(fm.didSucceed())
+ return fm.getResult();
+ else
+ return null;
+ }
+
+ private void generateValueDocumentation(Node container, String name) {
+ int p = name.lastIndexOf('/');
+ Module module;
+ if(p >= 0) {
+ while(p > 0 && name.charAt(p-1) == '/')
+ --p;
+ module = getModule(name.substring(0, p));
+ name = name.substring(p+1);
+ }
+ else
+ module = getRelatedModule();
+
+ if(module != null)
+ generateValueDocumentation(container, module, name);
+ }
+
+ private void generateValueDocumentation(Node container, Module module, String name, TypeUnparsingContext tuc) {
+ SCLValue value = module.getValue(name);
+ if(value == null) {
+ StringBuilder error = new StringBuilder();
+ error.append("Didn't find the value '" + name + "'.");
+ System.err.println(error);
+ container.addChild(new CodeBlockNode(error));
+ return;
+ }
+ if(value.isPrivate())
+ System.err.println("Documentation " + documentationName + " refers to a private value " + name + ".");
+
+ StringBuilder signature = new StringBuilder();
+ signature.append("<div id=\"")
+ .append(HtmlEscape.escape(name))
+ .append("\" class=\"code-doc-box\"><div class=\"code\">");
+ char firstChar = name.charAt(0);
+ if(!Character.isAlphabetic(firstChar) && firstChar != '_')
+ name = "(" + name + ")";
+ signature.append(HtmlEscape.escape(name)).append(" :: ");
+ String typeStr = Types.removeForAll(value.getType(), new ArrayList<TVar>()).toString(tuc);
+ signature.append(HtmlEscape.escape(typeStr));
+ String moduleName = module.getName();
+ if(!moduleName.equals(documentationName)) {
+ signature.append(" <span class=\"greyed\">(").append(moduleName).append(")</span>");
+ }
+ signature.append("</div><div class=\"doc\">");
+ container.addChild(new HtmlNode(signature));
+
+ if(value.documentation != null) {
+ MarkdownParser parser = new MarkdownParser();
+ container.addChild(parser.parseDocument(value.documentation));
+ }
+ else
+ System.out.println(name);
+ container.addChild(new HtmlNode("</div></div>"));
+ }
+
+ private void generateValueDocumentation(Node container, Module module, String name) {
+ generateValueDocumentation(container, module, name, new TypeUnparsingContext());
+ }
+
+ private void generateClassDocumentation(Node container, String name) {
+ int p = name.lastIndexOf('/');
+ Module module;
+ if(p >= 0) {
+ module = getModule(name.substring(0, p));
+ name = name.substring(p+1);
+ }
+ else
+ module = getRelatedModule();
+
+ if(module != null)
+ generateClassDocumentation(container, module, name);
+ }
+
+ private void generateClassDocumentation(Node container, Module module, String name) {
+ TypeClass typeClass = module.getTypeClass(name);
+ if(typeClass == null) {
+ StringBuilder error = new StringBuilder();
+ error.append("Didn't find the type class '" + name + "'.");
+ System.err.println(error);
+ container.addChild(new CodeBlockNode(error));
+ return;
+ }
+
+ TypeUnparsingContext tuc = new TypeUnparsingContext();
+ StringBuilder signature = new StringBuilder();
+ signature.append("<div class=\"code-doc-box\"><div class=\"code\">");
+ signature.append("class ");
+ if(typeClass.context.length > 0) {
+ signature.append('(');
+ boolean first = true;
+ for(TPred cx : typeClass.context) {
+ if(first)
+ first = false;
+ else
+ signature.append(", ");
+ cx.toString(tuc, signature);
+ }
+ signature.append(") => ");
+ }
+ signature.append(name);
+ for(TVar p : typeClass.parameters) {
+ signature.append(' ');
+ p.toName(tuc, signature);
+ }
+ signature.append("</div><div class=\"doc\">");
+ container.addChild(new HtmlNode(signature));
+
+ if(typeClass.documentation != null) {
+ MarkdownParser parser = new MarkdownParser();
+ container.addChild(parser.parseDocument(typeClass.documentation));
+ }
+ else
+ System.out.println(name);
+
+ for(String methodName : typeClass.methodNames) {
+ if(!documentedValues.add(methodName))
+ System.err.println("Method '" + methodName + "' has already been documented in " + documentationName + ".");
+ generateValueDocumentation(container, module, methodName, new TypeUnparsingContext(tuc));
+ }
+ container.addChild(new HtmlNode("</div></div>"));
+ }
+
+ private void generateDataDocumentation(Node container, String name) {
+ int p = name.lastIndexOf('/');
+ Module module;
+ if(p >= 0) {
+ module = getModule(name.substring(0, p));
+ name = name.substring(p+1);
+ }
+ else
+ module = getRelatedModule();
+
+ if(module != null)
+ generateDataDocumentation(container, module, name);
+ }
+
+ private void generateDataDocumentation(Node container, Module module, String name) {
+ TypeConstructor typeConstructor = module.getTypeConstructor(name);
+ if(typeConstructor == null) {
+ StringBuilder error = new StringBuilder();
+ error.append("Didn't find the type constructor '" + name + "'.");
+ System.err.println(error);
+ container.addChild(new CodeBlockNode(error));
+ return;
+ }
+
+ TypeUnparsingContext tuc = new TypeUnparsingContext();
+ StringBuilder signature = new StringBuilder();
+ signature.append("<div class=\"code-doc-box\"><div class=\"code\">");
+ signature.append("data ");
+ signature.append(typeConstructor.name.name);
+ for(TVar p : typeConstructor.parameters) {
+ signature.append(' ');
+ p.toName(tuc, signature);
+ }
+ String moduleName = module.getName();
+ if(!moduleName.equals(documentationName)) {
+ signature.append(" <span class=\"greyed\">(").append(moduleName).append(")</span>");
+ }
+ signature.append("</div><div class=\"doc\">");
+ container.addChild(new HtmlNode(signature));
+
+ if(typeConstructor.documentation != null) {
+ MarkdownParser parser = new MarkdownParser();
+ container.addChild(parser.parseDocument(typeConstructor.documentation));
+ }
+ else
+ System.out.println(name);
+
+ for(Constructor constructor : typeConstructor.constructors) {
+ if(!documentedValues.add(constructor.name.name))
+ System.err.println("Method '" + constructor.name.name + "' has already been documented in " + documentationName + ".");
+ generateValueDocumentation(container, module, constructor.name.name, new TypeUnparsingContext(tuc));
+ }
+ container.addChild(new HtmlNode("</div></div>"));
+ }
+}