X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scl.compiler%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fcompiler%2Fmarkdown%2Fhtml%2FSCLDocumentationExtensionNodeHandler.java;fp=bundles%2Forg.simantics.scl.compiler%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fcompiler%2Fmarkdown%2Fhtml%2FSCLDocumentationExtensionNodeHandler.java;h=6a505fabb272fe39786186ba25a655c8c9fe7708;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/markdown/html/SCLDocumentationExtensionNodeHandler.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/markdown/html/SCLDocumentationExtensionNodeHandler.java new file mode 100644 index 000000000..6a505fabb --- /dev/null +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/markdown/html/SCLDocumentationExtensionNodeHandler.java @@ -0,0 +1,321 @@ +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 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("
")); + } +}