001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.doc;
012
013import gololang.IO;
014
015import java.nio.file.Path;
016import java.util.*;
017
018public class CtagsProcessor extends AbstractProcessor {
019
020  private final LinkedList<String> ctags = new LinkedList<>();
021  private String file = "file";
022
023  private void ctagsLine(String name, String address, String field) {
024    ctags.add(String.format("%s\t%s\t%s;\"\t%s\tlanguage:golo%n", name, file, address, field));
025  }
026
027  private void ctagsModule(ModuleDocumentation module) {
028    ctagsLine(module.moduleName(),
029        "/^module[:blank:]+" + module.moduleName().replace(".", "\\.") + "$/",
030        "p\tline:" + module.moduleDefLine());
031  }
032
033  private void ctagsFunction(FunctionDocumentation funct) {
034    ctagsFunction(funct, "", false);
035  }
036
037  private void ctagsMacro(FunctionDocumentation macro) {
038    ctagsFunction(macro, "", false, "macro", "d");
039  }
040
041  private void ctagsFunction(FunctionDocumentation funct, String parent, boolean named) {
042    ctagsFunction(funct, parent, named, "function", "f");
043  }
044
045  private void ctagsFunction(FunctionDocumentation funct, String parent, boolean named, String keyword, String tag) {
046    String address = String.format("/%s[:blank:]+%s[:blank:]+=/", keyword, funct.name());
047
048    StringBuilder signature = new StringBuilder("\tsignature:(");
049    if (funct.arity() > 0) {
050      signature.append(funct.argument(0));
051      for (int i = 1; i < funct.arity(); i++) {
052        signature.append(", ").append(funct.argument(i));
053      }
054      if (funct.varargs()) { signature.append("..."); }
055    }
056    signature.append(")");
057
058    StringBuilder fields = new StringBuilder(tag);
059    fields.append("\tline:").append(funct.line());
060    if (funct.local()) {
061      fields.append("\taccess:private\tfile:");
062    } else {
063      fields.append("\taccess:public");
064    }
065    fields.append(signature);
066    if (!parent.isEmpty()) {
067      if (named) {
068        fields.append("\taugmentation:").append(parent);
069      } else {
070        fields.append("\taugment:").append(parent);
071      }
072    }
073    ctagsLine(funct.name(), address, fields.toString());
074  }
075
076  private void ctagsAugment(String name, int line) {
077    ctagsLine(name,
078        String.format("/^augment[:blank:]+%s/", name.replace(".", "\\.")),
079        String.format("a\tline:%s", line));
080  }
081
082  private void ctagsAugmentation(String name, int line) {
083    ctagsLine(name,
084        String.format("/^augmentation[:blank:]+%s[:blank:]+=[:blank:]+{/", name),
085        String.format("na\tline:%s", line));
086  }
087
088  private void ctagsStruct(String name, int line) {
089    ctagsLine(name,
090        String.format("/^struct[:blank:]+%s[:blank:]+=/", name),
091        String.format("s\tline:%s", line));
092  }
093
094  private void ctagsUnion(UnionDocumentation unionDoc) {
095    ctagsLine(unionDoc.name(),
096        String.format("/^union[:blank:]+%s[:blank:]+=[:blank:]+{/", unionDoc.name()),
097        String.format("g\tline:%s", unionDoc.line()));
098
099    for (UnionDocumentation.UnionValueDocumentation valueDoc : unionDoc.values()) {
100      ctagsUnionValue(unionDoc.name(), valueDoc);
101    }
102  }
103
104  private void ctagsUnionValue(String unionName, UnionDocumentation.UnionValueDocumentation valueDoc) {
105    ctagsLine(valueDoc.name(),
106        String.format("/[:blank:]+%s[:blank:]+%s", valueDoc.name(),
107                    valueDoc.hasMembers() ? "[:blank:]*=[:blank:]+{" : ""),
108        String.format("e\tline:%s\tunion:%s", valueDoc.line(), unionName));
109
110    for (MemberDocumentation member : valueDoc.members()) {
111      ctagsLine(member.name(),
112        String.format("/[:blank:]+%s[:blank:]+=/", valueDoc.name()),
113        String.format("m\tline:%s\taccess:public\tvalue:%s",
114          member.line(),
115          valueDoc.name()));
116    }
117  }
118
119  private void ctagsImport(String name, int line) {
120    ctagsLine(name,
121        String.format("/^import[:blank:]+%s/", name.replace(".", "\\.")),
122        String.format("i\tline:%s", line));
123  }
124
125  private void ctagsModState(String name, int line) {
126    ctagsLine(name,
127        String.format("(let|var)[:blank:]+%s[:blank:]+=/", name),
128        String.format("v\taccess:private\tfile:\tline:%s", line));
129  }
130
131  private void ctagsStructMember(String struct, String member, int line) {
132    ctagsLine(member,
133        String.format("/struct[:blank:]+%s[:blank:]+=/", struct),
134        String.format("m\tline:%s\taccess:%s\tstruct:%s",
135          line,
136          (member.charAt(0) == '_') ? "private" : "public",
137          struct));
138  }
139
140  private String ctagsAsString() {
141    java.util.Collections.sort(ctags);
142    StringBuilder buffer = new StringBuilder();
143    for (String line : ctags) {
144      buffer.append(line);
145    }
146    return buffer.toString();
147  }
148
149  @Override
150  public String render(ModuleDocumentation documentation) throws Throwable {
151    ctagsModule(documentation);
152    for (Map.Entry<String, Integer> imp : documentation.imports().entrySet()) {
153      ctagsImport(imp.getKey(), imp.getValue());
154    }
155    for (StructDocumentation struct : documentation.structs()) {
156      ctagsStruct(struct.name(), struct.line());
157      for (MemberDocumentation member : struct.members()) {
158        ctagsStructMember(struct.name(), member.name(), member.line());
159      }
160    }
161    for (UnionDocumentation unionDoc : documentation.unions()) {
162      ctagsUnion(unionDoc);
163    }
164    for (NamedAugmentationDocumentation augment : documentation.namedAugmentations()) {
165      ctagsAugmentation(augment.name(), augment.line());
166      for (FunctionDocumentation funct : augment.functions()) {
167        ctagsFunction(funct, augment.name(), true);
168      }
169    }
170    for (AugmentationDocumentation augment : documentation.augmentations()) {
171      ctagsAugment(augment.target(), augment.line());
172      for (FunctionDocumentation funct : augment.functions()) {
173        ctagsFunction(funct, augment.target(), false);
174      }
175    }
176    for (Map.Entry<String, Integer> state : documentation.moduleStates().entrySet()) {
177      ctagsModState(state.getKey(), state.getValue());
178    }
179    for (FunctionDocumentation funct : documentation.functions(true)) {
180      ctagsFunction(funct);
181    }
182    for (FunctionDocumentation funct : documentation.macros()) {
183      ctagsMacro(funct);
184    }
185    return ctagsAsString();
186  }
187
188  @Override
189  public void process(Collection<ModuleDocumentation> modules, Path targetFolder) throws Throwable {
190    Path targetFile = null;
191    if ("-".equals(targetFolder.toString())) {
192      targetFile = targetFolder;
193    } else {
194      targetFile = targetFolder.resolve("tags");
195    }
196    ctags.clear();
197    for (ModuleDocumentation doc : modules) {
198      file = doc.sourceFile();
199      render(doc);
200    }
201    IO.textToFile(ctagsAsString(), targetFile);
202  }
203}