package org.simantics.scl.compiler.markdown.html; 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; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; public class SCLDocumentationExtensionNodeHandler implements ExtensionNodeHandler { final ModuleRepository moduleRepository; final String documentationName; Failable relatedModule; THashMap> moduleCache = new THashMap>(); THashSet documentedValues = new THashSet(); THashSet documentedClasses = new THashSet(); THashSet documentedTypeConstructors = new THashSet(); 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 undocumentedValues = new ArrayList(); module.findValuesForPrefix("", AcceptAllNamespaceFilter.INSTANCE, new TObjectProcedure() { @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 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("
"); 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()).toString(tuc); signature.append(HtmlEscape.escape(typeStr)); String moduleName = module.getName(); if(!moduleName.equals(documentationName)) { signature.append(" (").append(moduleName).append(")"); } signature.append("
"); 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("
")); } 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("
"); 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("
"); 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("
")); } 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("
"); 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(" (").append(moduleName).append(")"); } signature.append("
"); 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("
")); } }