1
2
3
4
5
6 #include <gendoc/gendoc/Project.hpp>
7 #include <gendoc/html/Html.hpp>
8 #include <sngcpp/pp/PP.hpp>
9 #include <sngcpp/lexer/CppLexer.hpp>
10 #include <sngcpp/parser/SourceFile.hpp>
11 #include <sngcpp/symbols/SymbolCreator.hpp>
12 #include <sngcpp/binder/VirtualBinder.hpp>
13 #include <sngcpp/binder/StatementBinder.hpp>
14 #include <sngcpp/ast/Writer.hpp>
15 #include <sngcpp/ast/Reader.hpp>
16 #include <sngxml/xpath/XPathEvaluate.hpp>
17 #include <sngxml/xpath/XPathDebug.hpp>
18 #include <sngxml/dom/Parser.hpp>
19 #include <sngxml/dom/Document.hpp>
20 #include <sngxml/dom/Element.hpp>
21 #include <soulng/util/BinaryWriter.hpp>
22 #include <soulng/util/Path.hpp>
23 #include <soulng/util/System.hpp>
24 #include <soulng/util/TextUtils.hpp>
25 #include <soulng/util/Unicode.hpp>
26 #include <soulng/util/Util.hpp>
27 #include <boost/filesystem.hpp>
28 #include <boost/process.hpp>
29 #include <iostream>
30 #include <fstream>
31
32 namespace gendoc {
33
34 const int inlineCodeLimit = 10;
35
36 using namespace soulng::util;
37 using namespace soulng::unicode;
38
39 Import::Import(const std::u32string& name_, const std::string& relativePath_) : name(name_), relativePath(relativePath_)
40 {
41 }
42
43 Project::Project(const std::string& docFilePath_) :
44 docFilePath(docFilePath_), rootDir(Path::GetDirectoryName(docFilePath)), doc(sngxml::dom::ReadDocument(docFilePath)), contentXmlUpToDate(false), astGenerated(false), thisProjectIndex(-1),
45 childrenRead(false)
46 {
47 }
48
49 void Project::Process(bool verbose, bool rebuild, bool single)
50 {
51 ReadName();
52 RunChildren(verbose, rebuild, false, single);
53 GenerateAst(verbose, rebuild, true);
54 GenerateContent(verbose, rebuild, false, single);
55 }
56
57 void Project::Clean(bool verbose, bool single)
58 {
59 ReadName();
60 if (verbose)
61 {
62 std::cout << "cleaning " << ToUtf8(name) << "..." << std::endl;
63 }
64 RunChildren(verbose, false, true, single);
65 SetAstFilePath();
66 SetContentXmlFilePath();
67 SetHtmlFilePath();
68 boost::filesystem::remove(astFilePath);
69 boost::filesystem::remove(contentXmlFilePath);
70 boost::filesystem::remove(htmlFilePath);
71 boost::filesystem::remove(Path::Combine(rootDir, "status.msg"));
72 boost::filesystem::remove_all(Path::Combine(rootDir, "html"));
73 if (verbose)
74 {
75 std::cout << "> " << ToUtf8(name) << " cleaned" << std::endl;
76 }
77 }
78
79 void Project::ReadChildren()
80 {
81 if (childrenRead) return;
82 childrenRead = true;
83 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/child", doc.get());
84 if (result)
85 {
86 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
87 {
88 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
89 for (int i = 0; i < nodeSet->Length(); ++i)
90 {
91 sngxml::dom::Node* node = (*nodeSet)[i];
92 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
93 {
94 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
95 std::u32string project = element->GetAttribute(U"project");
96 if (!project.empty())
97 {
98 std::string childFilePath = Path::Combine(Path::Combine(rootDir, ToUtf8(project)), "gendoc.xml");
99 children.push_back(childFilePath);
100 std::u32string childProject = project;
101 std::unique_ptr<sngxml::dom::Document> childDoc = sngxml::dom::ReadDocument(childFilePath);
102 std::unique_ptr<sngxml::xpath::XPathObject> childResult = sngxml::xpath::Evaluate(U"/gendoc/project", childDoc.get());
103 if (childResult)
104 {
105 if (childResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
106 {
107 sngxml::xpath::XPathNodeSet* childNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(childResult.get());
108 for (int i = 0; i < childNodeSet->Length(); ++i)
109 {
110 sngxml::dom::Node* childNode = (*childNodeSet)[i];
111 if (childNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
112 {
113 sngxml::dom::Element* childElement = static_cast<sngxml::dom::Element*>(childNode);
114 std::u32string childName = childElement->GetAttribute(U"name");
115 if (!childName.empty())
116 {
117 childProject.append(1, '/').append(childName).append(U".html");
118 childProjects.push_back(std::move(childProject));
119 childNames.push_back(childName);
120 break;
121 }
122 }
123 }
124 }
125 }
126 }
127 }
128 }
129 }
130 }
131 }
132
133 void Project::RunChildren(bool verbose, bool rebuild, bool clean, bool single)
134 {
135 ReadChildren();
136 if (children.empty()) return;
137 if (clean)
138 {
139 if (verbose)
140 {
141 std::cout << ToUtf8(name) << "> cleaning " << children.size() << " child projects..." << std::endl;
142 }
143 if (single)
144 {
145 RunChildrenSingle(verbose, false, clean, false, false);
146 }
147 else
148 {
149 RunChildrenWithFlags(verbose, false, "l");
150 }
151 if (verbose)
152 {
153 std::cout << ToUtf8(name) << "> " << children.size() << " child projects cleaned." << std::endl;
154 }
155 }
156 else
157 {
158 if (verbose)
159 {
160 std::cout << ToUtf8(name) << "> building " << children.size() << " child projects..." << std::endl;
161 for (const auto& child : children)
162 {
163 boost::filesystem::remove(GetFullPath(Path::Combine(Path::GetDirectoryName(child), "status.msg")));
164 }
165 std::cout << ToUtf8(name) << "> generating ASTs..." << std::endl;
166 }
167 if (single)
168 {
169 RunChildrenSingle(verbose, rebuild, false, true, false);
170 }
171 else
172 {
173 RunChildrenWithFlags(verbose, rebuild, "a");
174 }
175 if (verbose)
176 {
177 std::cout << ToUtf8(name) << "> ASTs ready." << std::endl;
178 std::cout << ToUtf8(name) << "> generating content..." << std::endl;
179 }
180 if (single)
181 {
182 RunChildrenSingle(verbose, rebuild, false, false, true);
183 }
184 else
185 {
186 RunChildrenWithFlags(verbose, rebuild, "ce");
187 }
188 if (verbose)
189 {
190 std::cout << ToUtf8(name) << "> content ready." << std::endl;
191 for (const auto& child : children)
192 {
193 std::string statusMessageFilePath = GetFullPath(Path::Combine(Path::GetDirectoryName(child), "status.msg"));
194 if (boost::filesystem::exists(statusMessageFilePath))
195 {
196 std::string statusMessage = soulng::util::ReadFile(statusMessageFilePath);
197 std::cout << statusMessage;
198 }
199 }
200 std::cout << ToUtf8(name) << "> " << children.size() << " child projects built or up-to-date." << std::endl;
201 }
202 }
203 }
204
205 void Project::RunChildrenSingle(bool verbose, bool rebuild, bool clean, bool ast, bool content)
206 {
207 for (int i = 0; i < children.size(); ++i)
208 {
209 Project project(children[i]);
210 if (clean)
211 {
212 project.Clean(verbose, false);
213 }
214 else if (ast)
215 {
216 project.GenerateAst(verbose, rebuild, false);
217 }
218 else if (content)
219 {
220 project.GenerateContent(verbose, rebuild, false, false);
221 }
222 }
223 }
224
225 void Project::RunChildrenWithFlags(bool verbose, bool rebuild, const std::string& flags)
226 {
227 std::string f = "-";
228 if (verbose)
229 {
230 f.append("v");
231 }
232 if (rebuild)
233 {
234 f.append("r");
235 }
236 f.append(flags);
237 std::vector<boost::process::ipstream> is;
238 std::vector<boost::process::ipstream> es;
239 std::vector<boost::process::child> childProcesses;
240 for (int i = 0; i < children.size(); ++i)
241 {
242 if (verbose)
243 {
244 std::cout << ToUtf8(name) + ">> " << children[i] << std::endl;
245 }
246 is.push_back(boost::process::ipstream());
247 es.push_back(boost::process::ipstream());
248 boost::process::child child = boost::process::child(GetPathToExecutable(), f, children[i], boost::process::std_out > is[i], boost::process::std_err > es[i]);
249 childProcesses.push_back(std::move(child));
250 }
251 bool running = true;
252 while (running)
253 {
254 running = false;
255 for (int i = 0; i < childProcesses.size(); ++i)
256 {
257 if (childProcesses[i].running())
258 {
259 std::string line;
260 if (std::getline(is[i], line) && !line.empty())
261 {
262 std::cout << line << std::endl;
263 }
264 running = true;
265 }
266 }
267 }
268 for (int i = 0; i < childProcesses.size(); ++i)
269 {
270 childProcesses[i].wait();
271 int result = childProcesses[i].exit_code();
272 if (result != 0)
273 {
274 std::string line;
275 while (std::getline(es[i], line) && !line.empty())
276 {
277 std::cerr << line << std::endl;
278 }
279 throw std::runtime_error("child returned exit code " + std::to_string(result));
280 }
281 }
282 }
283
284 void Project::GenerateAst(bool verbose, bool rebuild, bool readAst)
285 {
286 ReadName();
287 ReadChildren();
288 if (!children.empty()) return;
289 ReadTop();
290 ReadParentNameAndSiblings();
291 ReadFilter();
292 ReadFilePaths(verbose);
293 ReadImports();
294 ReadIncludePath();
295 SetAstFilePath();
296 SetContentXmlFilePath();
297 if (BuildAst(verbose, rebuild))
298 {
299 WriteAst(verbose);
300 }
301 else if (readAst)
302 {
303 ReadAst(verbose);
304 }
305 astGenerated = true;
306 }
307
308 void Project::GenerateContent(bool verbose, bool rebuild, bool endMessage, bool single)
309 {
310 try
311 {
312 if (!astGenerated)
313 {
314 ReadName();
315 ReadChildren();
316 ReadTop();
317 ReadParentNameAndSiblings();
318 if (children.empty())
319 {
320 ReadFilter();
321 ReadFilePaths(verbose);
322 ReadImports();
323 ReadIncludePath();
324 SetAstFilePath();
325 SetContentXmlFilePath();
326 ReadAst(verbose);
327 astGenerated = true;
328 }
329 else
330 {
331 RunChildren(verbose, rebuild, false, single);
332 SetAstFilePath();
333 SetContentXmlFilePath();
334 }
335 }
336 if (children.empty())
337 {
338 ImportAsts(verbose);
339 BuildSymbolTable();
340 GenerateContentXml(verbose, rebuild);
341 }
342 MakeDirectories();
343 GenerateStyleSheet();
344 if (!rebuild)
345 {
346 upToDate = true;
347 }
348 else
349 {
350 upToDate = false;
351 }
352 GenerateHtmlCodeFiles(verbose, rebuild);
353 ReadGrammarXmlFiles(verbose);
354 GenerateHtmlContent(verbose, rebuild);
355 if (endMessage)
356 {
357 std::ofstream endFile(GetFullPath(Path::Combine(rootDir, "status.msg")));
358 if (upToDate)
359 {
360 endFile << ToUtf8(name) << ": up-to-date" << std::endl;
361 }
362 else
363 {
364 endFile << ToUtf8(name) << ": built" << std::endl;
365 }
366 }
367 else
368 {
369 if (upToDate)
370 {
371 if (verbose)
372 {
373 std::cout << ToUtf8(name) << "> up-to-date" << std::endl;
374 }
375 }
376 else
377 {
378 if (verbose)
379 {
380 std::cout << ToUtf8(name) << "> built" << std::endl;
381 }
382 }
383 }
384 }
385 catch (const std::exception& ex;)
386 {
387 if (endMessage)
388 {
389 std::ofstream endFile(GetFullPath(Path::Combine(rootDir, "status.msg")));
390 endFile << ToUtf8(name) << ": " << ex.what() << std::endl;
391 }
392 throw ;
393 }
394 }
395
396 void Project::SetAstFilePath()
397 {
398 astFilePath = Path::Combine(rootDir, ToUtf8(name) + ".ast");
399 }
400
401 void Project::SetContentXmlFilePath()
402 {
403 contentXmlFilePath = Path::Combine(rootDir, ToUtf8(name) + ".xml");
404 }
405
406 void Project::SetHtmlFilePath()
407 {
408 htmlFilePath = Path::Combine(rootDir, ToUtf8(name) + ".html");
409 }
410
411 void Project::ReadName()
412 {
413 if (!name.empty()) return;
414 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project", doc.get());
415 if (result)
416 {
417 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
418 {
419 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
420 for (int i = 0; i < nodeSet->Length(); ++i)
421 {
422 sngxml::dom::Node* node = (*nodeSet)[i];
423 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
424 {
425 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
426 if (name.empty())
427 {
428 name = element->GetAttribute(U"name");
429 }
430 else
431 {
432 throw std::runtime_error("project name already set");
433 }
434 }
435 }
436 }
437 }
438 if (name.empty())
439 {
440 throw std::runtime_error("project name not set in '" + docFilePath + "'");
441 }
442 }
443
444 void Project::ReadTop()
445 {
446 std::u32string top;
447 std::string parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir, ".."), "gendoc.xml"));
448 while (boost::filesystem::exists(parentXmlFilePath))
449 {
450 top = top + U"../";
451 std::string topXmlFilePath = parentXmlFilePath;
452 parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(Path::GetDirectoryName(parentXmlFilePath), ".."), "gendoc.xml"));
453 if (!boost::filesystem::exists(parentXmlFilePath))
454 {
455 std::unique_ptr<sngxml::dom::Document> topDoc = sngxml::dom::ReadDocument(topXmlFilePath);
456 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project", topDoc.get());
457 if (result)
458 {
459 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
460 {
461 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
462 for (int i = 0; i < nodeSet->Length(); ++i)
463 {
464 sngxml::dom::Node* node = (*nodeSet)[i];
465 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
466 {
467 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
468 std::u32string topName = element->GetAttribute(U"name");
469 if (!topName.empty())
470 {
471 top.append(topName).append(U".html");
472 topLink = top;
473 break;
474 }
475 }
476 }
477 }
478 }
479 }
480 }
481 }
482
483 void Project::ReadParentNameAndSiblings()
484 {
485 std::string parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir, ".."), "gendoc.xml"));
486 if (boost::filesystem::exists(parentXmlFilePath))
487 {
488 std::unique_ptr<sngxml::dom::Document> parentDoc = sngxml::dom::ReadDocument(parentXmlFilePath);
489 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project", parentDoc.get());
490 if (result)
491 {
492 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
493 {
494 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
495 for (int i = 0; i < nodeSet->Length(); ++i)
496 {
497 sngxml::dom::Node* node = (*nodeSet)[i];
498 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
499 {
500 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
501 if (parentName.empty())
502 {
503 parentName = element->GetAttribute(U"name");
504 std::unique_ptr<sngxml::xpath::XPathObject> siblingResult = sngxml::xpath::Evaluate(U"/gendoc/child", parentDoc.get());
505 if (siblingResult)
506 {
507 if (siblingResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
508 {
509 sngxml::xpath::XPathNodeSet* siblingNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(siblingResult.get());
510 for (int i = 0; i < siblingNodeSet->Length(); ++i)
511 {
512 sngxml::dom::Node* siblingNode = (*siblingNodeSet)[i];
513 if (siblingNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
514 {
515 sngxml::dom::Element* siblingElement = static_cast<sngxml::dom::Element*>(siblingNode);
516 std::u32string siblingDirName = siblingElement->GetAttribute(U"project");
517 if (!siblingDirName.empty())
518 {
519 std::u32string siblingProject = U"../" + siblingDirName;
520 std::string siblingXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir, ToUtf8(siblingProject)), "gendoc.xml"));
521 std::unique_ptr<sngxml::dom::Document> siblingDoc = sngxml::dom::ReadDocument(siblingXmlFilePath);
522 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project", siblingDoc.get());
523 if (result)
524 {
525 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
526 {
527 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
528 for (int j = 0; j < nodeSet->Length(); ++j)
529 {
530 sngxml::dom::Node* node = (*nodeSet)[j];
531 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
532 {
533 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
534 std::u32string siblingName = element->GetAttribute(U"name");
535 if (!siblingName.empty())
536 {
537 if (siblingName == name)
538 {
539 thisProjectIndex = i;
540 }
541 siblingProject.append(1, '/').append(siblingName).append(U".html");
542 siblingProjects.push_back(siblingProject);
543 break;
544 }
545 }
546 }
547 }
548 }
549 }
550 }
551 }
552 }
553 }
554 }
555 else
556 {
557 throw std::runtime_error("project parent name already set");
558 }
559 }
560 }
561 }
562 }
563 }
564 }
565
566 void Project::ReadFilter()
567 {
568 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/filter/file", doc.get());
569 if (result)
570 {
571 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
572 {
573 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
574 for (int i = 0; i < nodeSet->Length(); ++i)
575 {
576 sngxml::dom::Node* node = (*nodeSet)[i];
577 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
578 {
579 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
580 std::u32string excludePattern = element->GetAttribute(U"exclude");
581 if (!excludePattern.empty())
582 {
583 Filter filter(context, Filter::Type::exclude, excludePattern);
584 filters.Add(filter);
585 }
586 else
587 {
588 std::u32string includePattern = element->GetAttribute(U"include");
589 if (!includePattern.empty())
590 {
591 Filter filter(context, Filter::Type::include, includePattern);
592 filters.Add(filter);
593 }
594 }
595 }
596 }
597 }
598 }
599 }
600
601 void Project::ReadFilePaths(bool verbose)
602 {
603 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/vcxproject", doc.get());
604 if (result)
605 {
606 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
607 {
608 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
609 for (int i = 0; i < nodeSet->Length(); ++i)
610 {
611 sngxml::dom::Node* node = (*nodeSet)[i];
612 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
613 {
614 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
615 std::u32string fileAttribute = element->GetAttribute(U"file");
616 if (!fileAttribute.empty())
617 {
618 std::string vcxprojectFilePath = GetFullPath(Path::Combine(rootDir, ToUtf8(fileAttribute)));
619 if (verbose)
620 {
621 std::cout << ToUtf8(name) + "> " << vcxprojectFilePath << std::endl;
622 }
623 projectRoot = Path::GetDirectoryName(vcxprojectFilePath);
624 std::unique_ptr<sngxml::dom::Document> vcxprojectDoc = sngxml::dom::ReadDocument(vcxprojectFilePath);
625 std::unique_ptr<sngxml::xpath::XPathObject> hppFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/ClInclude", vcxprojectDoc.get());
626 if (hppFilesResult)
627 {
628 if (hppFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
629 {
630 sngxml::xpath::XPathNodeSet* hppFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(hppFilesResult.get());
631 for (int i = 0; i < hppFileNodeSet->Length(); ++i)
632 {
633 sngxml::dom::Node* hppFileNode = (*hppFileNodeSet)[i];
634 sngxml::dom::Element* hppFileElement = static_cast<sngxml::dom::Element*>(hppFileNode);
635 std::u32string hppFileName = hppFileElement->GetAttribute(U"Include");
636 if (!hppFileName.empty())
637 {
638 File hppFile(hppFileName, GetFullPath(Path::Combine(projectRoot, ToUtf8(hppFileName))));
639 filters.Apply(hppFile);
640 if (hppFile.Included())
641 {
642 filePaths.push_back(hppFile);
643 headerFiles.AddProjectHeaderFile(hppFile.Path());
644 }
645 }
646 }
647 }
648 }
649 std::unique_ptr<sngxml::xpath::XPathObject> cppFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/ClCompile", vcxprojectDoc.get());
650 if (cppFilesResult)
651 {
652 if (cppFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
653 {
654 sngxml::xpath::XPathNodeSet* cppFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(cppFilesResult.get());
655 for (int i = 0; i < cppFileNodeSet->Length(); ++i)
656 {
657 sngxml::dom::Node* cppFileNode = (*cppFileNodeSet)[i];
658 sngxml::dom::Element* cppFileElement = static_cast<sngxml::dom::Element*>(cppFileNode);
659 std::u32string cppFileName = cppFileElement->GetAttribute(U"Include");
660 if (!cppFileName.empty())
661 {
662 File cppFile(cppFileName, GetFullPath(Path::Combine(projectRoot, ToUtf8(cppFileName))));
663 filters.Apply(cppFile);
664 if (cppFile.Included())
665 {
666 filePaths.push_back(cppFile);
667 }
668 }
669 }
670 }
671 }
672 std::unique_ptr<sngxml::xpath::XPathObject> otherFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/None", vcxprojectDoc.get());
673 if (otherFilesResult)
674 {
675 if (otherFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
676 {
677 sngxml::xpath::XPathNodeSet* otherFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(otherFilesResult.get());
678 for (int i = 0; i < otherFileNodeSet->Length(); ++i)
679 {
680 sngxml::dom::Node* otherFileNode = (*otherFileNodeSet)[i];
681 sngxml::dom::Element* otherFileElement = static_cast<sngxml::dom::Element*>(otherFileNode);
682 std::u32string otherFileName = otherFileElement->GetAttribute(U"Include");
683 if (!otherFileName.empty())
684 {
685 File otherFile(otherFileName, GetFullPath(Path::Combine(projectRoot, ToUtf8(otherFileName))));
686 if (Path::GetExtension(otherFile.Path()) == ".parser" || Path::GetExtension(otherFile.Path()) == ".lexer")
687 {
688 filters.Apply(otherFile);
689 if (otherFile.Included())
690 {
691 filePaths.push_back(otherFile);
692 }
693 }
694 }
695 }
696 }
697 }
698 }
699 }
700 }
701 }
702 }
703 }
704
705 void Project::ReadImports()
706 {
707 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/imports/import", doc.get());
708 if (result)
709 {
710 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
711 {
712 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
713 for (int i = 0; i < nodeSet->Length(); ++i)
714 {
715 sngxml::dom::Node* node = (*nodeSet)[i];
716 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
717 {
718 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
719 std::string relativeImportDirPath = ToUtf8(element->GetAttribute(U"project"));
720 if (!relativeImportDirPath.empty())
721 {
722 std::string absoluteImportFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir, relativeImportDirPath), "gendoc.xml"));
723 std::unique_ptr<sngxml::dom::Document> importDoc = sngxml::dom::ReadDocument(absoluteImportFilePath);
724 std::unique_ptr<sngxml::xpath::XPathObject> importResult = sngxml::xpath::Evaluate(U"/gendoc/project", importDoc.get());
725 if (importResult)
726 {
727 if (importResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
728 {
729 sngxml::xpath::XPathNodeSet* importNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(importResult.get());
730 for (int i = 0; i < importNodeSet->Length(); ++i)
731 {
732 sngxml::dom::Node* importNode = (*importNodeSet)[i];
733 if (importNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
734 {
735 sngxml::dom::Element* importElement = static_cast<sngxml::dom::Element*>(importNode);
736 std::u32string importProjectName = importElement->GetAttribute(U"name");
737 Import* importProject = new Import(importProjectName, relativeImportDirPath);
738 importMap[importProjectName] = importProject;
739 imports.push_back(std::unique_ptr<Import>(importProject));
740 }
741 }
742 }
743 }
744 }
745 }
746 }
747 }
748 }
749 }
750
751 void Project::ReadIncludePath()
752 {
753 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/include", doc.get());
754 if (result)
755 {
756 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
757 {
758 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
759 for (int i = 0; i < nodeSet->Length(); ++i)
760 {
761 sngxml::dom::Node* node = (*nodeSet)[i];
762 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
763 {
764 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
765 std::u32string pathAttribute = element->GetAttribute(U"path");
766 if (!pathAttribute.empty())
767 {
768 if (includePath.empty())
769 {
770 includePath = Path::MakeCanonical(ToUtf8(pathAttribute));
771 }
772 else
773 {
774 throw std::runtime_error("include path already set");
775 }
776 }
777 }
778 }
779 }
780 }
781 }
782
783 bool Project::BuildAst(bool verbose, bool rebuild)
784 {
785 if (!rebuild && boost::filesystem::exists(astFilePath))
786 {
787 bool upToDate = true;
788 for (const File& file : filePaths)
789 {
790 if (boost::filesystem::last_write_time(file.Path()) >= boost::filesystem::last_write_time(astFilePath))
791 {
792 upToDate = false;
793 break;
794 }
795 }
796 if (upToDate)
797 {
798 return false;
799 }
800 }
801 sngcpp::pp::EvaluationContext evaluationContext;
802 int fileIndex = 0;
803 for (const File& file : filePaths)
804 {
805 if (file.Included())
806 {
807 std::unique_ptr<sngcpp::ast::SourceFileNode> sourceFile;
808 std::unique_ptr<CppLexer> lexer;
809 if (verbose)
810 {
811 std::cout << ToUtf8(name) + "> " << file.Path() << std::endl;
812 }
813 std::string ext = Path::GetExtension(file.Path());
814 if (ext == ".parser" || ext == ".lexer")
815 {
816 std::unique_ptr<std::u32string> content(new std::u32string(ToUtf32(soulng::util::ReadFile(file.Path()))));
817 sourceFile.reset(new sngcpp::ast::SourceFileNode(soulng::lexer::Span(), file.Path(), ToUtf8(file.Name()), name));
818 sourceFile->SetSourceFileIndex(fileIndex);
819 sourceFile->SetText(std::move(*content));
820 }
821 else
822 {
823 sngcpp::pp::PP pp(evaluationContext);
824 pp.root = projectRoot;
825 pp.includePath = Split(includePath, ';');
826 pp.projectHeaderFileSet = &headerFiles;
827 pp.Define(sngcpp::pp::ndebug);
828 if (verbose)
829 {
830 pp.verbose = true;
831 }
832 Preprocess(file.Path(), &pp);
833 std::unique_ptr<std::u32string> content(new std::u32string(std::move(pp.ctext)));
834 lexer.reset(new CppLexer(content->c_str(), content->c_str() + content->length(), file.Path(), fileIndex));
835 lexer->SetSeparatorChar('\n');
836 sourceFile.reset(new sngcpp::ast::SourceFileNode(lexer->GetSpan(), file.Path(), ToUtf8(file.Name()), name));
837 sourceFile->SetSourceFileIndex(fileIndex);
838 SourceFileParser::Parse(*lexer, sourceFile.get());
839 sourceFile->SetHeaderFilePaths(std::move(pp.headerFilePaths));
840 sourceFile->SetText(std::move(*content));
841 sourceFile->ComputeLineStarts();
842 }
843 if (ast)
844 {
845 soulng::lexer::Span span = ast->GetSpan();
846 ast.reset(new sngcpp::ast::SourceFileSequenceNode(span, ast.release(), sourceFile.release()));
847 }
848 else
849 {
850 ast.reset(sourceFile.release());
851 }
852 ++fileIndex;
853 lexers.push_back(std::unique_ptr<soulng::lexer::Lexer>(lexer.release()));
854 }
855 }
856 return true;
857 }
858
859 void Project::WriteAst(bool verbose)
860 {
861 if (!ast) return;
862 BinaryWriter binaryWriter(astFilePath);
863 std::vector< soulng::lexer::Lexer*> lexerPtrs;
864 for (const auto& lexer : lexers)
865 {
866 lexerPtrs.push_back(lexer.get());
867 }
868 sngcpp::ast::Writer writer(lexerPtrs, binaryWriter);
869 ast->Write(writer);
870 if (verbose)
871 {
872 std::cout << ToUtf8(name) + "==> " << astFilePath << std::endl;
873 }
874 }
875
876 void Project::ReadAst(bool verbose)
877 {
878 if (!boost::filesystem::exists(astFilePath)) return;
879 if (verbose)
880 {
881 std::cout << ToUtf8(name) + "> " << astFilePath << std::endl;
882 }
883 BinaryReader binaryReader(astFilePath);
884 sngcpp::ast::Reader reader(binaryReader);
885 ast.reset(reader.ReadNode());
886 }
887
888 void Project::ImportAsts(bool verbose)
889 {
890 for (const auto& importProject : imports)
891 {
892 std::string importAstFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir, importProject->RelativePath()), ToUtf8(importProject->Name()) + ".ast"));
893 importAstFilePaths.push_back(importAstFilePath);
894 if (verbose)
895 {
896 std::cout << ToUtf8(name) + "> " << importAstFilePath << std::endl;
897 }
898 BinaryReader binaryReader(importAstFilePath);
899 sngcpp::ast::Reader reader(binaryReader);
900 importAsts.push_back(std::unique_ptr<sngcpp::ast::Node>(reader.ReadNode()));
901 }
902 }
903
904 void Project::BuildSymbolTable()
905 {
906 if (!ast) return;
907 symbolTable.SetGendocMode();
908 sngcpp::ast::ResolveSourceFiles(ast.get(), sourceFiles);
909 for (auto& sourceFile : sourceFiles)
910 {
911 sourceFileMap[sourceFile->SourceFilePath()] = sourceFile;
912 }
913 int n = imports.size();
914 for (int i = 0; i < n; ++i)
915 {
916 Import* importProject = imports[i].get();
917 std::std::unique_ptr<sngcpp::ast::Node>&importAst=importAsts[i];
918 sngcpp::symbols::SymbolCreator symbolCreator(symbolTable, importProject->Name());
919 importAst->Accept(symbolCreator);
920 importSourceFiles.push_back(std::vector<sngcpp::ast::SourceFileNode*>());
921 sngcpp::ast::ResolveSourceFiles(importAst.get(), importSourceFiles.back());
922 for (auto& sourceFile : importSourceFiles.back())
923 {
924 sourceFileMap[sourceFile->SourceFilePath()] = sourceFile;
925 }
926 sngcpp::binder::TypeBinder typeBinder(symbolTable, this);
927 importAst->Accept(typeBinder);
928 }
929 sngcpp::symbols::SymbolCreator symbolCreator(symbolTable, name);
930 ast->Accept(symbolCreator);
931 sngcpp::binder::TypeBinder typeBinder(symbolTable, this);
932 ast->Accept(typeBinder);
933 sngcpp::binder::ResolveOverrideSets(typeBinder.ClassesHavingVirtualFunctions());
934 std::vector<std::std::unique_ptr<sngcpp::binder::BoundSourceFile>>boundSourceFiles=typeBinder.SourceFiles();
935 std::unordered_map<sngcpp::ast::SourceFileNode*, sngcpp::binder::BoundSourceFile*> boundSourceFileMap = typeBinder.SourceFileMap();
936 sngcpp::binder::StatementBinder statementBinder(symbolTable, boundSourceFileMap);
937 ast->Accept(statementBinder);
938 }
939
940 void Project::GenerateContentXml(bool verbose, bool rebuild)
941 {
942 if (!boost::filesystem::exists(astFilePath)) return;
943 contentXmlUpToDate = true;
944 if (rebuild)
945 {
946 contentXmlUpToDate = false;
947 }
948 else
949 {
950 if (boost::filesystem::exists(contentXmlFilePath))
951 {
952 if (boost::filesystem::last_write_time(astFilePath) > boost::filesystem::last_write_time(contentXmlFilePath))
953 {
954 contentXmlUpToDate = false;
955 }
956 else
957 {
958 for (const std::string& importAstFilePath : importAstFilePaths)
959 {
960 if (boost::filesystem::last_write_time(importAstFilePath) > boost::filesystem::last_write_time(contentXmlFilePath))
961 {
962 contentXmlUpToDate = false;
963 break;
964 }
965 }
966 if (contentXmlUpToDate)
967 {
968 for (const auto& sourceFileVec : importSourceFiles)
969 {
970 for (const auto& sourceFile : sourceFileVec)
971 {
972 if (boost::filesystem::last_write_time(sourceFile->SourceFilePath()) > boost::filesystem::last_write_time(contentXmlFilePath))
973 {
974 contentXmlUpToDate = false;
975 break;
976 }
977 }
978 if (!contentXmlUpToDate)
979 {
980 break;
981 }
982 }
983 }
984 }
985 }
986 else
987 {
988 contentXmlUpToDate = false;
989 }
990 }
991 if (contentXmlUpToDate)
992 {
993 ReadContentXml(verbose);
994 }
995 else
996 {
997 WriteContentXml(verbose);
998 }
999 }
1000
1001 void Project::WriteContentXml(bool verbose)
1002 {
1003 contentXml = symbolTable.ToDomDocument();
1004 std::ofstream xmlFile(contentXmlFilePath);
1005 CodeFormatter formatter(xmlFile);
1006 formatter.SetIndentSize(1);
1007 contentXml->Write(formatter);
1008 if (verbose)
1009 {
1010 std::cout << ToUtf8(name) + "==> " << contentXmlFilePath << std::endl;
1011 }
1012 }
1013
1014 void Project::ReadContentXml(bool verbose)
1015 {
1016 if (verbose)
1017 {
1018 std::cout << ToUtf8(name) + "> " << contentXmlFilePath << std::endl;
1019 }
1020 contentXml = sngxml::dom::ReadDocument(contentXmlFilePath);
1021 }
1022
1023 void Project::MakeDirectories()
1024 {
1025 gendoc::html::MakeDirectories(rootDir, htmlDir, contentDir, fileDir, styleDir);
1026 }
1027
1028 void Project::GenerateStyleSheet()
1029 {
1030 gendoc::html::GenerateStyleSheet(styleDir, styleDirName, styleFileName);
1031 }
1032
1033 void Project::GenerateHtmlCodeFiles(bool verbose, bool rebuild)
1034 {
1035 for (auto& sourceFile : sourceFiles)
1036 {
1037 sourceFile->SetHtmlSourceFilePath(Path::Combine(contentDir, ToUtf8(sourceFile->Id())));
1038 }
1039 bool htmlCodeUpToDate = true;
1040 gendoc::html::GenerateHtmlCodeFiles(name, sourceFiles, styleDirName, styleFileName, symbolTable, inlineCodeLimit, inlineCodeMap, verbose, rebuild, htmlCodeUpToDate, this);
1041 if (!htmlCodeUpToDate)
1042 {
1043 upToDate = false;
1044 }
1045 }
1046
1047 void Project::ReadGrammarXmlFiles(bool verbose)
1048 {
1049 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/grammars", doc.get());
1050 if (result)
1051 {
1052 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1053 {
1054 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1055 int n = nodeSet->Length();
1056 for (int i = 0; i < n; ++i)
1057 {
1058 sngxml::dom::Node* node = (*nodeSet)[i];
1059 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1060 {
1061 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1062 std::u32string grammarsFileAttribute = element->GetAttribute(U"file");
1063 if (!grammarsFileAttribute.empty())
1064 {
1065 grammarsXmlFilePath = GetFullPath(Path::Combine(rootDir, Path::MakeCanonical(ToUtf8(grammarsFileAttribute))));
1066 grammarsXmlDoc = sngxml::dom::ReadDocument(grammarsXmlFilePath);
1067 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/project/grammar", grammarsXmlDoc.get());
1068 if (result)
1069 {
1070 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1071 {
1072 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1073 int n = nodeSet->Length();
1074 for (int i = 0; i < n; ++i)
1075 {
1076 sngxml::dom::Node* node = (*nodeSet)[i];
1077 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1078 {
1079 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1080 std::u32string grammarFileAttribute = element->GetAttribute(U"file");
1081 if (!grammarFileAttribute.empty())
1082 {
1083 std::u32string grammarHtmlFileName = ToUtf32(Path::Combine("html/content",
1084 Path::GetFileNameWithoutExtension(Path::MakeCanonical(ToUtf8(grammarFileAttribute))) + ".html"));
1085 std::string grammarXmlFilePath = GetFullPath(Path::Combine(rootDir, Path::MakeCanonical(ToUtf8(grammarFileAttribute))));
1086 std::unique_ptr<sngxml::dom::Document> grammarXmlDoc = sngxml::dom::ReadDocument(grammarXmlFilePath);
1087 std::u32string grammarTitle = grammarXmlDoc->DocumentElement()->GetAttribute(U"title");
1088 gendoc::html::Grammar grammar(grammarHtmlFileName, grammarTitle);
1089 grammars.push_back(std::move(grammar));
1090 }
1091 }
1092 }
1093 }
1094 }
1095 }
1096 }
1097 }
1098 }
1099 }
1100 }
1101
1102 void Project::ReadDocumentationXml(bool verbose, bool& rebuild)
1103 {
1104 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/documentation", doc.get());
1105 if (result)
1106 {
1107 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1108 {
1109 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1110 int n = nodeSet->Length();
1111 for (int i = 0; i < n; ++i)
1112 {
1113 sngxml::dom::Node* node = (*nodeSet)[i];
1114 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1115 {
1116 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1117 std::u32string documentationFileAttribute = element->GetAttribute(U"file");
1118 if (!documentationFileAttribute.empty())
1119 {
1120 documentationXmlFileName = GetFullPath(Path::Combine(rootDir, Path::MakeCanonical(ToUtf8(documentationFileAttribute))));
1121 if (boost::filesystem::exists(documentationXmlFileName))
1122 {
1123 documentationXml = sngxml::dom::ReadDocument(documentationXmlFileName);
1124 if (boost::filesystem::exists(contentXmlFilePath))
1125 {
1126 if (boost::filesystem::last_write_time(documentationXmlFileName) > boost::filesystem::last_write_time(contentXmlFilePath))
1127 {
1128 rebuild = true;
1129 }
1130 }
1131 }
1132 else
1133 {
1134 std::cerr << "Documentation XML file '" + documentationXmlFileName + "' does not exist." << std::endl;
1135 }
1136 }
1137 }
1138 }
1139 }
1140 }
1141 }
1142
1143 void Project::GenerateHtmlContent(bool verbose, bool rebuild)
1144 {
1145 ReadDocumentationXml(verbose, rebuild);
1146 if (contentXml)
1147 {
1148 bool htmlContentUpToDate = true;
1149 std::u32string top;
1150 if (!topLink.empty())
1151 {
1152 top = U"../../";
1153 top.append(topLink);
1154 }
1155 gendoc::html::GenerateContent(name, contentXml.get(), contentDir, styleDirName, styleFileName, inlineCodeMap, contentXmlFilePath, verbose, rebuild, htmlContentUpToDate, this, top,
1156 documentationXmlFileName, documentationXml.get());
1157 if (!htmlContentUpToDate)
1158 {
1159 upToDate = false;
1160 }
1161 }
1162 bool moduleFileUpToDate = true;
1163 std::u32string prevProject;
1164 std::u32string nextProject;
1165 if (thisProjectIndex != -1)
1166 {
1167 if (thisProjectIndex > 0)
1168 {
1169 prevProject = siblingProjects[thisProjectIndex - 1];
1170 }
1171 if (thisProjectIndex < siblingProjects.size() - 1)
1172 {
1173 nextProject = siblingProjects[thisProjectIndex + 1];
1174 }
1175 }
1176 gendoc::html::GenerateModuleHtml(name, parentName, prevProject, nextProject, childProjects, childNames, contentXml.get(), contentDir, styleDirName, styleFileName, sourceFiles,
1177 contentXmlFilePath, verbose, rebuild, moduleFileUpToDate, this, topLink, documentationXmlFileName, documentationXml.get(), grammars);
1178 if (!moduleFileUpToDate)
1179 {
1180 upToDate = false;
1181 }
1182 }
1183
1184 std::string Project::ResolveContentFilePath(const std::u32string& currentProjectName, const std::u32string& projectName, const std::string& relativeContentDirPath, const std::u32string& symbolId)
1185 {
1186 if (projectName.empty())
1187 {
1188 std::cerr << "warning: import for project name is empty" << std::endl;
1189 return Path::Combine(relativeContentDirPath, ToUtf8(symbolId) + ".html");
1190 }
1191 else if (currentProjectName == projectName)
1192 {
1193 return Path::Combine(relativeContentDirPath, ToUtf8(symbolId) + ".html");
1194 }
1195 else
1196 {
1197 auto it = importMap.find(projectName);
1198 if (it != importMap.cend())
1199 {
1200 Import* imp = it->second;
1201 return Path::Combine(Path::Combine(Path::Combine(Path::Combine(relativeContentDirPath, "../.."), imp->RelativePath()), "html/content"), ToUtf8(symbolId) + ".html");
1202 }
1203 else
1204 {
1205 std::cerr << "warning: import for project name " + ToUtf8(projectName) + " not found" << std::endl;
1206 return Path::Combine(relativeContentDirPath, ToUtf8(symbolId) + ".html");
1207 }
1208 }
1209 }
1210
1211 sngcpp::ast::SourceFileNode* Project::GetSourceFile(const std::string& sourceFilePath) const
1212 {
1213 auto it = sourceFileMap.find(sourceFilePath);
1214 if (it != sourceFileMap.cend())
1215 {
1216 return it->second;
1217 }
1218 else
1219 {
1220 return nullptr;
1221 }
1222 }
1223
1224 }