1 /******************************************************************************* 2 * Copyright: © 2014 Economic Modeling Specialists, Intl. 3 * Author: Brian Schott 4 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 5 * 6 * Forked and modified in 2021 for hgen by Eugene 'Vindex' Stulin. 7 * The 'hgen' project: https://gitlab.com/vindexbit/hgen 8 * Author: Eugene 'Vindex' Stulin <tech.vindex@gmail.com> 9 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 10 */ 11 12 module ddoc.comments; 13 14 import std.algorithm : among, each, find, startsWith, endsWith; 15 import std.exception : enforce; 16 import std.string : strip, toLower; 17 18 import ddoc.sections; 19 import ddoc.lexer; 20 import ddoc.lexer : ParseExc = DdocParseException; 21 import ddoc.highlight : highlight; 22 import ddoc.macros : expand, KeyValuePair, parseKeyValuePair; 23 24 25 /******************************************************************************* 26 * Converts D comment test to Comment structure object. 27 * 28 * Params: 29 * text = doc string 30 * macros = associative array storing the names and values of macros 31 * removeUnknown = whether to remove unknown macros 32 */ 33 Comment parseComment(string text, 34 string[string] macros = null, 35 bool removeUnknown = true) 36 out(retVal) { 37 assert(retVal.sections.length >= 2); 38 } 39 do { 40 auto sections = splitSections(text); 41 string[string] sMacros = macros.dup; 42 auto m = sections.find!(p => p.name == "Macros"); 43 const e = sections.find!(p => p.name == "Escapes"); 44 auto p = sections.find!(p => p.name == "Params"); 45 if (m.length) { 46 enforce( 47 doMapping(m[0]), 48 new ParseExc("Unable to parse Key/Value pairs", m[0].content) 49 ); 50 foreach (kv; m[0].mapping) { 51 sMacros[kv[0]] = kv[1]; 52 } 53 } 54 if (e.length) { 55 assert(0, "Escapes not handled yet"); 56 } 57 if (p.length) { 58 enforce( 59 doMapping(p[0]), // p[0] - we can have one 'params' section only 60 new ParseExc("Unable to parse Key/Value pairs", p[0].content) 61 ); 62 foreach (ref kv; p[0].mapping) { 63 kv[1] = expand(Lexer(highlight(kv[1])), sMacros, removeUnknown); 64 } 65 } 66 67 foreach (ref Section s; sections) { 68 if (among(s.name, "Macros", "Escapes", "Params")) { 69 continue; 70 } 71 s.content = expand(Lexer(highlight(s.content)), sMacros, removeUnknown); 72 } 73 74 return Comment(sections); 75 } 76 77 78 /******************************************************************************* 79 * Structure containing a set of doc comment sections. 80 */ 81 struct Comment { 82 /// Array of comment sections. 83 Section[] sections; 84 85 /// Whether the comment refer to the previous one by the phrase "ditto". 86 bool isDitto() const @property { 87 if (sections.length != 2) { 88 return false; 89 } 90 return sections[0].content.strip().toLower() == "ditto"; 91 } 92 } 93 94 95 /// Matches parameter names to their descriptions 96 private bool doMapping(ref Section s) { 97 auto lex = Lexer(s.content); 98 KeyValuePair[] pairs; 99 if (!parseKeyValuePair(lex, pairs)) { 100 return false; 101 } 102 foreach (kv; pairs) { 103 s.mapping ~= kv; 104 } 105 return true; 106 } 107 108 109 unittest { 110 Comment test = parseComment("Description\nParams:\n x = thing\n"); 111 assert(test.sections.length == 3); 112 assert(test.sections[0].name == ""); 113 assert(test.sections[0].content == "Description"); 114 assert(test.sections[2].name == "Params"); 115 assert(test.sections[2].content == "x = thing"); 116 assert(test.sections[2].mapping[0][0] == "x"); 117 assert(test.sections[2].mapping[0][1] == "thing"); 118 } 119 120 121 unittest { 122 auto macros = ["A" : "<a href=\"$0\">"]; 123 auto comment = `Best-comment-ever © 2014 124 125 I thought the same. I was considering writing it, actually. 126 Imagine how having the $(A tool) would have influenced the "final by 127 default" discussion. Amongst others, of course. 128 129 It essentially comes down to persistent compiler-as-a-library 130 issue. Tools like dscanner can help with some of more simple 131 transition cases but anything more complicated is likely to 132 require full semantic analysis. 133 Params: 134 a = $(A param) 135 Returns: 136 nothing of consequence 137 `; 138 139 Comment c = parseComment(comment, macros); 140 import std.string : format; 141 142 assert(c.sections.length == 4); 143 144 assert(c.sections[0].name is null); 145 assert(c.sections[0].content == "Best-comment-ever © 2014"); 146 147 assert(c.sections[1].name is null); 148 assert(c.sections[1].content.startsWith("I thought the same")); 149 assert(c.sections[1].content.endsWith("full semantic analysis.")); 150 151 assert(c.sections[2].name == "Params"); 152 assert(c.sections[2].mapping[0][0] == "a"); 153 assert(c.sections[2].mapping[0][1] == `<a href="param">`); 154 155 assert(c.sections[3].name == "Returns"); 156 assert(c.sections[3].content == "nothing of consequence"); 157 } 158 159 160 unittest { 161 auto macros = ["A" : "<a href=\"$0\">"]; 162 auto text = `Best $(Unknown comment) ever`; 163 164 Comment c = parseComment(text, macros, true); 165 assert(c.sections.length >= 1); 166 assert(c.sections[0].name is null); 167 assert(c.sections[0].content == "Best ever"); 168 169 c = parseComment(text, macros, false); 170 assert(c.sections.length >= 1); 171 assert(c.sections[0].name is null); 172 assert(c.sections[0].content == "Best $(Unknown comment) ever"); 173 } 174 175 176 unittest { 177 auto comment = `--- 178 auto subcube(T...)(T values); 179 --- 180 Creates a new cube in a similar way to whereCube, but allows the user to 181 define a new root for specific dimensions.`c; 182 string[string] macros; 183 const Comment c = parseComment(comment, macros); 184 } 185 186 187 /// 188 unittest { 189 import std.conv : text; 190 191 auto s1 = `Stop the world 192 193 This function tells the Master to stop the world, taking effect immediately. 194 195 Params: 196 reason = Explanation to give to the $(B Master) 197 duration = Time for which the world $(UNUSED)would be stopped 198 (as time itself stop, this is always $(F double.infinity)) 199 200 --- 201 void main() { 202 import std.datetime : msecs; 203 import master.universe.control; 204 stopTheWorld("Too fast", 42.msecs); 205 assert(0); // Will never be reached. 206 } 207 --- 208 209 Returns: 210 Nothing, because nobody can restart it. 211 212 Macros: 213 F= $0`; 214 215 immutable expectedExamples = `<pre class="d_code">` 216 ~ "<font color=blue>void</font> main() {\n" 217 ~ " <font color=blue>import</font> std.datetime : msecs;\n" 218 ~ " <font color=blue>import</font> master.universe.control;\n" 219 ~ " stopTheWorld(<font color=red>\"Too fast\"</font>, 42.msecs);\n" 220 ~ " <font color=blue>assert</font>(0); " 221 ~ "<font color=green>// Will never be reached.</font>\n" 222 ~ "}</pre>"; 223 224 auto c = parseComment(s1, null); 225 226 assert(c.sections.length == 6); 227 assert(c.sections[0].name is null); 228 assert(c.sections[0].content == "Stop the world"); 229 230 assert(c.sections[1].name is null); 231 const fnDescr = `This function tells the Master to stop the world, ` 232 ~ `taking effect immediately.`; 233 assert(c.sections[1].content == fnDescr); 234 235 auto s2 = c.sections[2]; 236 237 assert(s2.name == "Params"); 238 assert(s2.mapping[0][0] == "reason"); 239 assert(s2.mapping[0][1] == "Explanation to give to the <b>Master</b>"); 240 assert(s2.mapping[1][0] == "duration"); 241 const durDescr = "Time for which the world would be stopped\n " 242 ~ "(as time itself stop, this is always double.infinity)"; 243 assert(s2.mapping[1][1] == durDescr); 244 245 assert(c.sections[3].name == "Examples"); 246 assert(c.sections[3].content == expectedExamples); 247 248 assert(c.sections[4].name == "Returns"); 249 assert(c.sections[4].content == "Nothing, because nobody can restart it."); 250 251 assert(c.sections[5].name == "Macros"); 252 assert(c.sections[5].mapping[0][0] == "F"); 253 assert(c.sections[5].mapping[0][1] == "$0"); 254 } 255 256 257 unittest { 258 import std.stdio : writeln, writefln; 259 260 auto comment = `Unrolled Linked List. 261 262 Nodes are (by default) sized to fit within a 64-byte cache line. The number 263 of items stored per node can be read from the $(B nodeCapacity) field. 264 See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list) 265 Params: 266 T = the element type 267 supportGC = true to ensure that the GC scans the nodes of the unrolled 268 list, false if you are sure that no references to GC-managed memory 269 will be stored in this container. 270 cacheLineSize = Nodes will be sized to fit within this number of bytes.`; 271 272 auto parsed = parseComment(comment, null); 273 assert(parsed.sections[3].name == "Params"); 274 assert(parsed.sections[3].mapping.length == 3); 275 assert(parsed.sections[3].mapping[0][0] == "T"); 276 assert(parsed.sections[3].mapping[0][1] == "the element type"); 277 assert(parsed.sections[3].mapping[1][0] == "supportGC"); 278 assert(parsed.sections[3].mapping[1][1].startsWith("true to ensure")); 279 assert(parsed.sections[3].mapping[2][0] == "cacheLineSize"); 280 assert(parsed.sections[3].mapping[2][1].startsWith("Nodes will be sized")); 281 }