Joos1W Compiler Framework
All Classes Functions Typedefs Pages
CompilerPasses.cc
1 #include "CompilerPasses.h"
2 
3 #include <memory>
4 #include <string_view>
5 
6 #include "ast/DeclContext.h"
7 #include "diagnostics/Diagnostics.h"
8 #include "diagnostics/Location.h"
9 #include "diagnostics/SourceManager.h"
10 #include "grammar/Joos1WGrammar.h"
11 #include "parsetree/ParseTreeVisitor.h"
12 #include "semantic/HierarchyChecker.h"
13 #include "semantic/NameResolver.h"
14 #include "semantic/Semantic.h"
15 #include "third-party/CLI11.h"
16 #include "utils/BumpAllocator.h"
17 #include "utils/PassManager.h"
18 #include "utils/Utils.h"
19 
20 using std::string_view;
21 using utils::Pass;
22 using utils::PassManager;
23 
24 namespace joos1 {
25 
26 /* ===--------------------------------------------------------------------=== */
27 // Joos1WParserPass
28 /* ===--------------------------------------------------------------------=== */
29 
30 void Joos1WParserPass::Run() {
31  // Print the file being parsed if verbose
32  if(PM().Diag().Verbose()) {
33  auto os = PM().Diag().ReportDebug();
34  os << "Parsing file ";
35  SourceManager::print(os.get(), file_);
36  }
37  // Check for non-ASCII characters
38  checkNonAscii(SourceManager::getBuffer(file_));
39  // Parse the file
40  BumpAllocator alloc{NewHeap()};
41  Joos1WParser parser{file_, alloc, &PM().Diag()};
42  int result = parser.parse(tree_);
43  // If no parse tree was generated, report error if not already reported
44  if((result != 0 || !tree_) && !PM().Diag().hasErrors())
45  PM().Diag().ReportError(SourceRange{file_}) << "failed to parse file";
46  if(result != 0 || !tree_) return;
47  // If the parse tree is poisoned, report error
48  if(tree_->is_poisoned()) {
49  PM().Diag().ReportError(SourceRange{file_}) << "parse tree is poisoned";
50  return;
51  }
52  // If the parse tree has invalid literal types, report error
53  if(!isLiteralTypeValid(tree_)) {
54  PM().Diag().ReportError(SourceRange{file_})
55  << "invalid literal types in parse tree";
56  return;
57  }
58 }
59 
60 bool Joos1WParserPass::isLiteralTypeValid(parsetree::Node* node) {
61  if(node == nullptr) return true;
62  if(node->get_node_type() == parsetree::Node::Type::Literal)
63  return static_cast<parsetree::Literal*>(node)->isValid();
64  for(size_t i = 0; i < node->num_children(); i++) {
65  if(!isLiteralTypeValid(node->child(i))) return false;
66  }
67  return true;
68 }
69 
70 void Joos1WParserPass::checkNonAscii(std::string_view str) {
71  for(unsigned i = 0; i < str.length(); i++) {
72  if(static_cast<unsigned char>(str[i]) > 127) {
73  PM().Diag().ReportError(SourceRange{file_})
74  << "non-ASCII character in file";
75  return;
76  }
77  }
78 }
79 
80 /* ===--------------------------------------------------------------------=== */
81 // AstContextPass
82 /* ===--------------------------------------------------------------------=== */
83 
84 void AstContextPass::Init() {
85  // Grab a long-living heap
86  alloc = std::make_unique<BumpAllocator>(NewHeap());
87 }
88 
89 void AstContextPass::Run() {
90  sema = std::make_unique<ast::Semantic>(*alloc, PM().Diag());
91 }
92 
93 AstContextPass::~AstContextPass() {
94  // FIXME(kevin): This is a really bad bug that occurs in many places
95  sema.reset();
96  alloc.reset();
97 }
98 
99 /* ===--------------------------------------------------------------------=== */
100 // AstBuilderPass
101 /* ===--------------------------------------------------------------------=== */
102 
103 /// @brief Prints the parse tree back to the parent node
104 /// @param node Node to trace back to the parent
105 static inline void trace_node(parsetree::Node const* node, std::ostream& os) {
106  if(node->parent() != nullptr) {
107  trace_node(node->parent(), os);
108  os << " -> ";
109  }
110  os << node->type_string() << std::endl;
111 }
112 
113 /// @brief Marks a node and all its parents
114 /// @param node The node to mark
115 static inline void mark_node(parsetree::Node* node) {
116  if(!node) return;
117  mark_node(node->parent());
118  node->mark();
119 }
120 
121 AstBuilderPass::AstBuilderPass(PassManager& PM, Joos1WParserPass& dep) noexcept
122  : Pass(PM), dep{dep} {}
123 
124 void AstBuilderPass::Init() {
125  optCheckName = PM().PO().GetExistingOption("--enable-filename-check");
126 }
127 
128 void AstBuilderPass::Run() {
129  // Get the parse tree and the semantic analysis
130  auto& sema = GetPass<AstContextPass>().Sema();
131  auto* PT = dep.Tree();
132  // Create a new heap just for creating the AST
133  BumpAllocator alloc{NewHeap()};
134  parsetree::ParseTreeVisitor visitor{sema, alloc};
135  // Store the result in the pass
136  try {
137  cu_ = visitor.visitCompilationUnit(PT);
138  } catch(const parsetree::ParseTreeException& e) {
139  PM().Diag().ReportError(SourceRange{}) << "ParseTreeException occured";
140  std::cerr << "ParseTreeException: " << e.what() << " in file ";
141  SourceManager::print(std::cerr, dep.File());
142  std::cerr << std::endl;
143  std::cerr << "Parse tree trace:" << std::endl;
144  trace_node(e.get_where(), std::cerr);
145  return;
146  }
147  if(cu_ == nullptr && !PM().Diag().hasErrors()) {
148  PM().Diag().ReportError(PT->location()) << "failed to build AST";
149  return;
150  }
151  // Check if the file name matches the class name
152  bool shouldCheck = optCheckName && optCheckName->as<bool>();
153  std::pmr::string fileName{SourceManager::getFileName(dep.File()), alloc};
154  if(!fileName.empty() && shouldCheck) {
155  auto cuBody = cu_->bodyAsDecl();
156  // Grab the file without the path and the extension
157  fileName = fileName.substr(0, fileName.find_last_of('.'));
158  fileName = fileName.substr(fileName.find_last_of('/') + 1);
159  if(cuBody->name() != fileName) {
160  PM().Diag().ReportError(cuBody->location())
161  << "class/interface name does not match file name: "
162  << cuBody->name() << " != " << fileName;
163  return;
164  }
165  }
166 }
167 
168 /* ===--------------------------------------------------------------------=== */
169 // LinkerPass
170 /* ===--------------------------------------------------------------------=== */
171 
172 void LinkerPass::Run() {
173  std::pmr::vector<ast::CompilationUnit*> cus{};
174  // Get the semantic analysis
175  auto& sema = GetPass<AstContextPass>().Sema();
176  // Create the linking unit
177  for(auto* pass : GetPasses<AstBuilderPass>())
178  cus.push_back(pass->CompilationUnit());
179  lu_ = sema.BuildLinkingUnit(cus);
180 }
181 
182 /* ===--------------------------------------------------------------------=== */
183 // HierarchyCheckerPass
184 /* ===--------------------------------------------------------------------=== */
185 
186 void HierarchyCheckerPass::Run() {
187  auto lu = GetPass<LinkerPass>().LinkingUnit();
188  checker.Check(lu);
189  for(auto* cu : lu->compliationUnits()) {
190  auto* classDecl = dyn_cast_or_null<ast::ClassDecl>(cu->body());
191  if(!classDecl) continue;
192  // Check for each class in the LU, the super classes have a default ctor
193  for(auto* super : classDecl->superClasses()) {
194  if(!super) continue;
195  if(!dyn_cast_or_null<ast::ClassDecl>(super->decl())) continue;
196  if(cast<ast::ClassDecl>(super->decl())->hasDefaultCtor()) continue;
197  PM().Diag().ReportError(super->location())
198  << "super class "
199  << (super->decl()->hasCanonicalName()
200  ? super->decl()->getCanonicalName()
201  : super->decl()->name())
202  << " of "
203  << (classDecl->hasCanonicalName() ? classDecl->getCanonicalName()
204  : classDecl->name())
205  << " does not have a default constructor";
206  break;
207  }
208  }
209 }
210 
211 /* ===--------------------------------------------------------------------=== */
212 // NameResolverPass
213 /* ===--------------------------------------------------------------------=== */
214 
215 void NameResolverPass::Init() {
216  alloc = std::make_unique<BumpAllocator>(NewHeap());
217 }
218 
219 void NameResolverPass::Run() {
220  auto lu = GetPass<LinkerPass>().LinkingUnit();
221  auto sema = &GetPass<AstContextPass>().Sema();
222  NR = std::make_unique<semantic::NameResolver>(*alloc, PM().Diag());
223  NR->Init(lu, sema);
224  if(PM().Diag().hasErrors()) return;
225  resolveRecursive(lu);
226 }
227 
228 void NameResolverPass::replaceObjectClass(ast::AstNode* node) {
229  auto decl = dyn_cast_or_null<ast::ClassDecl*>(node);
230  if(!decl) return;
231  // Check if the class is Object
232  if(decl != NR->GetJavaLang().Object) return;
233  // Go through the superclasses and replace Object with nullptr
234  for(int i = 0; i < 2; i++) {
235  auto super = decl->superClasses()[i];
236  if(!super) continue;
237  // If the superclass is not resolved, then we should just bail out
238  if(!PM().Diag().hasErrors())
239  assert(super->isResolved() && "Superclass should be resolved");
240  else
241  continue;
242  // Do not allow Object to extend Object
243  if(super->decl() == NR->GetJavaLang().Object)
244  decl->mut_superClasses()[i] = nullptr;
245  }
246 }
247 
248 void NameResolverPass::resolveExpr(ast::Expr* expr) {
249  if(!expr) return;
250  for(auto node : expr->mut_nodes()) {
251  auto tyNode = dyn_cast<ast::exprnode::TypeNode>(node);
252  if(!tyNode) continue;
253  if(tyNode->isTypeResolved()) continue;
254  tyNode->resolveUnderlyingType(*NR);
255  }
256 }
257 
258 void NameResolverPass::resolveRecursive(ast::AstNode* node) {
259  assert(node && "Node must not be null here!");
260  for(auto child : node->mut_children()) {
261  if(!child) continue;
262  if(auto cu = dyn_cast<ast::CompilationUnit*>(child)) {
263  // If the CU has no body, then we can skip to the next CU :)
264  if(!cu->body()) return;
265  // Resolve the current compilation unit's body
266  NR->BeginContext(cu);
267  resolveRecursive(cu->mut_body());
268  replaceObjectClass(cu->mut_body());
269  NR->EndContext();
270  } else if(auto ty = dyn_cast<ast::Type*>(child)) {
271  if(ty->isInvalid()) continue;
272  // If the type is not resolved, then we should resolve it
273  if(!ty->isResolved()) ty->resolve(*NR);
274  } else {
275  // Resolve any Type in expressions
276  if(auto decl = dyn_cast<ast::TypedDecl*>(child)) {
277  resolveExpr(decl->mut_init());
278  } else if(auto stmt = dyn_cast<ast::Stmt*>(child)) {
279  for(auto expr : stmt->mut_exprs()) resolveExpr(expr);
280  }
281  // This is a generic node, just resolve its children
282  resolveRecursive(child);
283  }
284  }
285 }
286 
287 NameResolverPass::~NameResolverPass() {
288  // FIXME(kevin): This is a really bad bug that occurs in many places
289  NR.reset();
290  alloc.reset();
291 }
292 
293 /* ===--------------------------------------------------------------------=== */
294 // PrintASTPass
295 /* ===--------------------------------------------------------------------=== */
296 
297 class PrintASTPass final : public Pass {
298 public:
299  PrintASTPass(PassManager& PM) noexcept : Pass(PM) {}
300  string_view Name() const override { return "print-ast"; }
301  string_view Desc() const override { return "Print AST"; }
302 
303  void Init() override {
304  optDot = PM().PO().GetExistingOption("--print-dot");
305  optOutput = PM().PO().GetExistingOption("--print-output");
306  optSplit = PM().PO().GetExistingOption("--print-split");
307  optIgnoreStd = PM().PO().GetExistingOption("--print-ignore-std");
308  }
309 
310  void Run() override {
311  // 1a. Grab the AST node
312  auto& pass = GetPass<LinkerPass>();
313  auto* node = pass.LinkingUnit();
314  if(!node) return;
315  // 1b. Create a new heap to build a new AST if we're ignoring stdlib
316  BumpAllocator alloc{NewHeap()};
317  ast::Semantic newSema{alloc, PM().Diag()};
318  if(optIgnoreStd && optIgnoreStd->count()) {
319  std::pmr::vector<ast::CompilationUnit*> cus{alloc};
320  for(auto* cu : node->compliationUnits())
321  if(!cu->isStdLib()) cus.push_back(cu);
322  node = newSema.BuildLinkingUnit(cus);
323  }
324  // 2. Interpret the command line options
325  bool printDot = optDot && optDot->count();
326  bool printSplit = optSplit && optSplit->count();
327  std::string outputPath = optOutput ? optOutput->as<std::string>() : "";
328  // 3. Print the AST depending on the options
329  if(!printSplit) {
330  // 1. Now we try to open the output file if exists
331  std::ofstream file{};
332  if(!outputPath.empty()) {
333  file = std::ofstream{outputPath};
334  if(!file.is_open()) {
335  PM().Diag().ReportError(SourceRange{})
336  << "failed to open output file " << outputPath;
337  return;
338  }
339  }
340  // 2. Set the output stream
341  std::ostream& os = file.is_open() ? file : std::cout;
342  // 3. Now we can print the AST
343  if(printDot)
344  node->printDot(os);
345  else
346  node->print(os);
347  } else {
348  namespace fs = std::filesystem;
349  // 1. Create the output directory if it doesn't exist
350  fs::create_directories(outputPath);
351  // 2. Create a new file for each compilation unit
352  for(auto const& cu : node->compliationUnits()) {
353  if(!cu->body() || !cu->bodyAsDecl()->hasCanonicalName()) continue;
354  std::string canonName{cu->bodyAsDecl()->getCanonicalName()};
355  auto filepath = fs::path{outputPath} / fs::path{canonName + ".dot"};
356  std::ofstream file{filepath};
357  if(!file.is_open()) {
358  PM().Diag().ReportError(SourceRange{})
359  << "failed to open output file " << filepath.string();
360  return;
361  }
362  // 3. Set the output stream
363  std::ostream& os = file;
364  // 4. Now we can print the AST
365  cu->printDot(os);
366  }
367  }
368  }
369 
370 private:
371  void computeDependencies() override {
372  ComputeDependency(GetPass<LinkerPass>());
373  ComputeDependency(GetPass<AstContextPass>());
374  }
375  CLI::Option *optDot, *optOutput, *optSplit, *optIgnoreStd;
376 };
377 
378 } // namespace joos1
379 
380 /* ===--------------------------------------------------------------------=== */
381 // Register the passes
382 /* ===--------------------------------------------------------------------=== */
383 
384 REGISTER_PASS_NS(joos1, AstContextPass);
385 REGISTER_PASS_NS(joos1, LinkerPass);
386 REGISTER_PASS_NS(joos1, NameResolverPass);
387 REGISTER_PASS_NS(joos1, HierarchyCheckerPass);
388 REGISTER_PASS_NS(joos1, PrintASTPass);
389 
390 Pass& NewJoos1WParserPass(PassManager& PM, SourceFile file, Pass* prev) {
391  using namespace joos1;
392  return PM.AddPass<Joos1WParserPass>(file, prev);
393 }
394 
395 Pass& NewAstBuilderPass(PassManager& PM, Pass* depends) {
396  using namespace joos1;
397  // "depends" must be a Joos1WParserPass
398  auto* p = cast<Joos1WParserPass*>(depends);
399  return PM.AddPass<AstBuilderPass>(*p);
400 }