From d867d962d82abae8b40ed5e433a02646a5aacbe0 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Thu, 23 Feb 2017 00:18:54 -0800 Subject: [PATCH] Small code cleanups/renames --- compilation_database_loader.cc | 1 - indexer.cpp | 92 ++++++++++++++-------------- indexer.h | 36 +++++------ query_db.cc | 107 +++++++++++++++++++-------------- utils.cc | 7 +++ utils.h | 4 +- 6 files changed, 135 insertions(+), 112 deletions(-) diff --git a/compilation_database_loader.cc b/compilation_database_loader.cc index b21134f4..09b8d84c 100644 --- a/compilation_database_loader.cc +++ b/compilation_database_loader.cc @@ -13,7 +13,6 @@ std::vector LoadCompilationEntriesFromDirectory(const std::str exit(1); } - CXCompileCommands cx_commands = clang_CompilationDatabase_getAllCompileCommands(cx_db); unsigned int num_commands = clang_CompileCommands_getSize(cx_commands); diff --git a/indexer.cpp b/indexer.cpp index a30bd3ed..1f9ad536 100644 --- a/indexer.cpp +++ b/indexer.cpp @@ -1,59 +1,59 @@ #include "indexer.h" -ParsingDatabase::ParsingDatabase() {} +IndexedFile::IndexedFile() {} // TODO: Optimize for const char*? -TypeId ParsingDatabase::ToTypeId(const std::string& usr) { +TypeId IndexedFile::ToTypeId(const std::string& usr) { auto it = usr_to_type_id.find(usr); if (it != usr_to_type_id.end()) return it->second; TypeId id(types.size()); - types.push_back(TypeDef(id, usr)); + types.push_back(IndexedTypeDef(id, usr)); usr_to_type_id[usr] = id; return id; } -FuncId ParsingDatabase::ToFuncId(const std::string& usr) { +FuncId IndexedFile::ToFuncId(const std::string& usr) { auto it = usr_to_func_id.find(usr); if (it != usr_to_func_id.end()) return it->second; FuncId id(funcs.size()); - funcs.push_back(FuncDef(id, usr)); + funcs.push_back(IndexedFuncDef(id, usr)); usr_to_func_id[usr] = id; return id; } -VarId ParsingDatabase::ToVarId(const std::string& usr) { +VarId IndexedFile::ToVarId(const std::string& usr) { auto it = usr_to_var_id.find(usr); if (it != usr_to_var_id.end()) return it->second; VarId id(vars.size()); - vars.push_back(VarDef(id, usr)); + vars.push_back(IndexedVarDef(id, usr)); usr_to_var_id[usr] = id; return id; } -TypeId ParsingDatabase::ToTypeId(const CXCursor& cursor) { +TypeId IndexedFile::ToTypeId(const CXCursor& cursor) { return ToTypeId(clang::Cursor(cursor).get_usr()); } -FuncId ParsingDatabase::ToFuncId(const CXCursor& cursor) { +FuncId IndexedFile::ToFuncId(const CXCursor& cursor) { return ToFuncId(clang::Cursor(cursor).get_usr()); } -VarId ParsingDatabase::ToVarId(const CXCursor& cursor) { +VarId IndexedFile::ToVarId(const CXCursor& cursor) { return ToVarId(clang::Cursor(cursor).get_usr()); } -TypeDef* ParsingDatabase::Resolve(TypeId id) { +IndexedTypeDef* IndexedFile::Resolve(TypeId id) { return &types[id.local_id]; } -FuncDef* ParsingDatabase::Resolve(FuncId id) { +IndexedFuncDef* IndexedFile::Resolve(FuncId id) { return &funcs[id.local_id]; } -VarDef* ParsingDatabase::Resolve(VarId id) { +IndexedVarDef* IndexedFile::Resolve(VarId id) { return &vars[id.local_id]; } @@ -148,7 +148,7 @@ void Write(Writer& writer, const char* key, uint64_t value) { writer.Uint64(value); } -std::string ParsingDatabase::ToString() { +std::string IndexedFile::ToString() { auto it = usr_to_type_id.find(""); if (it != usr_to_type_id.end()) { Resolve(it->second)->short_name = ""; @@ -168,7 +168,7 @@ std::string ParsingDatabase::ToString() { // Types writer.Key("types"); writer.StartArray(); - for (TypeDef& def : types) { + for (IndexedTypeDef& def : types) { if (def.is_system_def) continue; writer.StartObject(); @@ -191,7 +191,7 @@ std::string ParsingDatabase::ToString() { // Functions writer.Key("functions"); writer.StartArray(); - for (FuncDef& def : funcs) { + for (IndexedFuncDef& def : funcs) { if (def.is_system_def) continue; writer.StartObject(); @@ -215,7 +215,7 @@ std::string ParsingDatabase::ToString() { // Variables writer.Key("variables"); writer.StartArray(); - for (VarDef& def : vars) { + for (IndexedVarDef& def : vars) { if (def.is_system_def) continue; writer.StartObject(); @@ -404,7 +404,7 @@ struct NamespaceHelper { }; struct IndexParam { - ParsingDatabase* db; + IndexedFile* db; NamespaceHelper* ns; // Record the last type usage location we recorded. Clang will sometimes @@ -413,7 +413,7 @@ struct IndexParam { Location last_type_usage_location; Location last_func_usage_location; - IndexParam(ParsingDatabase* db, NamespaceHelper* ns) : db(db), ns(ns) {} + IndexParam(IndexedFile* db, NamespaceHelper* ns) : db(db), ns(ns) {} }; /* @@ -443,19 +443,19 @@ bool IsTypeDefinition(const CXIdxContainerInfo* container) { struct VisitDeclForTypeUsageParam { - ParsingDatabase* db; + IndexedFile* db; bool is_interesting; int has_processed_any = false; optional previous_cursor; optional initial_type; - VisitDeclForTypeUsageParam(ParsingDatabase* db, bool is_interesting) + VisitDeclForTypeUsageParam(IndexedFile* db, bool is_interesting) : db(db), is_interesting(is_interesting) {} }; void VisitDeclForTypeUsageVisitorHandler(clang::Cursor cursor, VisitDeclForTypeUsageParam* param) { param->has_processed_any = true; - ParsingDatabase* db = param->db; + IndexedFile* db = param->db; // TODO: Something in STL (type_traits)? reports an empty USR. std::string referenced_usr = cursor.get_referenced().get_usr(); @@ -467,7 +467,7 @@ void VisitDeclForTypeUsageVisitorHandler(clang::Cursor cursor, VisitDeclForTypeU param->initial_type = ref_type_id; if (param->is_interesting) { - TypeDef* ref_type_def = db->Resolve(ref_type_id); + IndexedTypeDef* ref_type_def = db->Resolve(ref_type_id); Location loc = db->file_db.Resolve(cursor, true /*interesting*/); ref_type_def->AddUsage(loc); } @@ -496,7 +496,7 @@ clang::VisiterResult VisitDeclForTypeUsageVisitor(clang::Cursor cursor, clang::C return clang::VisiterResult::Continue; } -optional ResolveDeclToType(ParsingDatabase* db, clang::Cursor decl_cursor, +optional ResolveDeclToType(IndexedFile* db, clang::Cursor decl_cursor, bool is_interesting, const CXIdxContainerInfo* semantic_container, const CXIdxContainerInfo* lexical_container) { // @@ -563,7 +563,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { bool is_system_def = clang_Location_isInSystemHeader(clang_getCursorLocation(decl->cursor)); IndexParam* param = static_cast(client_data); - ParsingDatabase* db = param->db; + IndexedFile* db = param->db; NamespaceHelper* ns = param->ns; switch (decl->entityInfo->kind) { @@ -580,7 +580,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { { clang::Cursor decl_cursor = decl->cursor; VarId var_id = db->ToVarId(decl->entityInfo->USR); - VarDef* var_def = db->Resolve(var_id); + IndexedVarDef* var_def = db->Resolve(var_id); var_def->is_system_def = is_system_def; @@ -610,7 +610,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { if (decl->isDefinition && IsTypeDefinition(decl->semanticContainer)) { TypeId declaring_type_id = db->ToTypeId(decl->semanticContainer->cursor); - TypeDef* declaring_type_def = db->Resolve(declaring_type_id); + IndexedTypeDef* declaring_type_def = db->Resolve(declaring_type_id); var_def->declaring_type = declaring_type_id; declaring_type_def->vars.push_back(var_id); } @@ -627,7 +627,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { { clang::Cursor decl_cursor = decl->cursor; FuncId func_id = db->ToFuncId(decl->entityInfo->USR); - FuncDef* func_def = db->Resolve(func_id); + IndexedFuncDef* func_def = db->Resolve(func_id); func_def->is_system_def = is_system_def; @@ -653,7 +653,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { // be one of those in the entire program. if (IsTypeDefinition(decl->semanticContainer)) { TypeId declaring_type_id = db->ToTypeId(decl->semanticContainer->cursor); - TypeDef* declaring_type_def = db->Resolve(declaring_type_id); + IndexedTypeDef* declaring_type_def = db->Resolve(declaring_type_id); func_def->declaring_type = declaring_type_id; // Mark a type reference at the ctor/dtor location. @@ -716,7 +716,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { for (unsigned int i = 0; i < num_overridden; ++i) { clang::Cursor parent = overridden[i]; FuncId parent_id = db->ToFuncId(parent.get_usr()); - FuncDef* parent_def = db->Resolve(parent_id); + IndexedFuncDef* parent_def = db->Resolve(parent_id); func_def = db->Resolve(func_id); // ToFuncId invalidated func_def func_def->base = parent_id; @@ -744,7 +744,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { optional alias_of = ResolveDeclToType(db, decl->cursor, true /*is_interesting*/, decl->semanticContainer, decl->lexicalContainer); TypeId type_id = db->ToTypeId(decl->entityInfo->USR); - TypeDef* type_def = db->Resolve(type_id); + IndexedTypeDef* type_def = db->Resolve(type_id); type_def->is_system_def = is_system_def; @@ -766,7 +766,7 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { case CXIdxEntity_CXXClass: { TypeId type_id = db->ToTypeId(decl->entityInfo->USR); - TypeDef* type_def = db->Resolve(type_id); + IndexedTypeDef* type_def = db->Resolve(type_id); type_def->is_system_def = is_system_def; @@ -805,9 +805,9 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { const CXIdxBaseClassInfo* base_class = class_info->bases[i]; optional parent_type_id = ResolveDeclToType(db, base_class->cursor, true /*is_interesting*/, decl->semanticContainer, decl->lexicalContainer); - TypeDef* type_def = db->Resolve(type_id); // type_def ptr could be invalidated by ResolveDeclToType. + IndexedTypeDef* type_def = db->Resolve(type_id); // type_def ptr could be invalidated by ResolveDeclToType. if (parent_type_id) { - TypeDef* parent_type_def = db->Resolve(parent_type_id.value()); + IndexedTypeDef* parent_type_def = db->Resolve(parent_type_id.value()); parent_type_def->derived.push_back(type_id); type_def->parents.push_back(parent_type_id.value()); } @@ -842,7 +842,7 @@ bool IsFunction(CXCursorKind kind) { void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { IndexParam* param = static_cast(client_data); - ParsingDatabase* db = param->db; + IndexedFile* db = param->db; clang::Cursor cursor(ref->cursor); switch (ref->referencedEntity->kind) { @@ -852,7 +852,7 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re case CXIdxEntity_Field: { VarId var_id = db->ToVarId(ref->referencedEntity->cursor); - VarDef* var_def = db->Resolve(var_id); + IndexedVarDef* var_def = db->Resolve(var_id); var_def->uses.push_back(db->file_db.Resolve(ref->loc, false /*interesting*/)); break; } @@ -883,15 +883,15 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re FuncId called_id = db->ToFuncId(ref->referencedEntity->USR); if (IsFunction(ref->container->cursor.kind)) { FuncId caller_id = db->ToFuncId(ref->container->cursor); - FuncDef* caller_def = db->Resolve(caller_id); - FuncDef* called_def = db->Resolve(called_id); + IndexedFuncDef* caller_def = db->Resolve(caller_id); + IndexedFuncDef* called_def = db->Resolve(called_id); caller_def->callees.push_back(FuncRef(called_id, loc)); called_def->callers.push_back(FuncRef(caller_id, loc)); called_def->uses.push_back(loc); } else { - FuncDef* called_def = db->Resolve(called_id); + IndexedFuncDef* called_def = db->Resolve(called_id); called_def->uses.push_back(loc); } @@ -906,9 +906,9 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re Location parent_loc = db->file_db.Resolve(ref->parentEntity->cursor, true /*interesting*/); Location our_loc = db->file_db.Resolve(ref->loc, true /*is_interesting*/); if (!parent_loc.IsEqualTo(our_loc)) { - FuncDef* called_def = db->Resolve(called_id); + IndexedFuncDef* called_def = db->Resolve(called_id); assert(called_def->declaring_type.has_value()); - TypeDef* type_def = db->Resolve(called_def->declaring_type.value()); + IndexedTypeDef* type_def = db->Resolve(called_def->declaring_type.value()); type_def->AddUsage(our_loc); } } @@ -923,7 +923,7 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re case CXIdxEntity_CXXClass: { TypeId referenced_id = db->ToTypeId(ref->referencedEntity->USR); - TypeDef* referenced_def = db->Resolve(referenced_id); + IndexedTypeDef* referenced_def = db->Resolve(referenced_id); // // The following will generate two TypeRefs to Foo, both located at the @@ -964,7 +964,7 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re static bool DUMP_AST = true; -ParsingDatabase Parse(std::string filename, std::vector args) { +IndexedFile Parse(std::string filename, std::vector args) { clang::Index index(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/); clang::TranslationUnit tu(index, filename, args); @@ -987,7 +987,7 @@ ParsingDatabase Parse(std::string filename, std::vector args) { */ }; - ParsingDatabase db; + IndexedFile db; NamespaceHelper ns; IndexParam param(&db, &ns); clang_indexTranslationUnit(index_action, ¶m, callbacks, sizeof(callbacks), @@ -1089,7 +1089,7 @@ void WriteToFile(const std::string& filename, const std::string& content) { file << content; } -int mai2n(int argc, char** argv) { +int main(int argc, char** argv) { // TODO: Assert that we need to be on clang >= 3.9.1 /* @@ -1128,7 +1128,7 @@ int mai2n(int argc, char** argv) { // Run test. std::cout << "[START] " << path << std::endl; - ParsingDatabase db = Parse(path, {}); + IndexedFile db = Parse(path, {}); std::string actual_output = db.ToString(); //WriteToFile("output.json", actual_output); diff --git a/indexer.h b/indexer.h index 1dc7a775..9d8e03cf 100644 --- a/indexer.h +++ b/indexer.h @@ -80,11 +80,11 @@ Location WithInteresting(bool interesting) { END_BITFIELD_TYPE() -struct FileDb { +struct IndexedFileDb { std::unordered_map file_path_to_file_id; std::unordered_map file_id_to_file_path; - FileDb() { + IndexedFileDb() { // Reserve id 0 for unfound. file_path_to_file_id[""] = 0; file_id_to_file_path[0] = ""; @@ -167,7 +167,7 @@ using VarRef = Ref; // TODO: Either eliminate the defs created as a by-product of cross-referencing, // or do not emit things we don't have definitions for. -struct TypeDef { +struct IndexedTypeDef { bool is_system_def = false; // General metadata. @@ -204,7 +204,7 @@ struct TypeDef { // NOTE: Do not insert directly! Use AddUsage instead. std::vector uses; - TypeDef(TypeId id, const std::string& usr) : id(id), usr(usr) { + IndexedTypeDef(TypeId id, const std::string& usr) : id(id), usr(usr) { assert(usr.size() > 0); //std::cout << "Creating type with usr " << usr << std::endl; } @@ -226,7 +226,7 @@ struct TypeDef { } }; -struct FuncDef { +struct IndexedFuncDef { bool is_system_def = false; // General metadata. @@ -260,12 +260,12 @@ struct FuncDef { // All usages. For interesting usages, see callees. std::vector uses; - FuncDef(FuncId id, const std::string& usr) : id(id), usr(usr) { + IndexedFuncDef(FuncId id, const std::string& usr) : id(id), usr(usr) { assert(usr.size() > 0); } }; -struct VarDef { +struct IndexedVarDef { bool is_system_def = false; // General metadata. @@ -287,26 +287,26 @@ struct VarDef { // Usages. std::vector uses; - VarDef(VarId id, const std::string& usr) : id(id), usr(usr) { + IndexedVarDef(VarId id, const std::string& usr) : id(id), usr(usr) { assert(usr.size() > 0); } }; -struct ParsingDatabase { +struct IndexedFile { // NOTE: Every Id is resolved to a file_id of 0. The correct file_id needs // to get fixed up when inserting into the real db. std::unordered_map usr_to_type_id; std::unordered_map usr_to_func_id; std::unordered_map usr_to_var_id; - std::vector types; - std::vector funcs; - std::vector vars; + std::vector types; + std::vector funcs; + std::vector vars; - FileDb file_db; + IndexedFileDb file_db; - ParsingDatabase(); + IndexedFile(); TypeId ToTypeId(const std::string& usr); FuncId ToFuncId(const std::string& usr); @@ -315,11 +315,11 @@ struct ParsingDatabase { FuncId ToFuncId(const CXCursor& usr); VarId ToVarId(const CXCursor& usr); - TypeDef* Resolve(TypeId id); - FuncDef* Resolve(FuncId id); - VarDef* Resolve(VarId id); + IndexedTypeDef* Resolve(TypeId id); + IndexedFuncDef* Resolve(FuncId id); + IndexedVarDef* Resolve(VarId id); std::string ToString(); }; -ParsingDatabase Parse(std::string filename, std::vector args); \ No newline at end of file +IndexedFile Parse(std::string filename, std::vector args); \ No newline at end of file diff --git a/query_db.cc b/query_db.cc index d7b9018c..28cdec4f 100644 --- a/query_db.cc +++ b/query_db.cc @@ -24,7 +24,7 @@ struct SymbolIdx { }; }; -struct File { +struct QueryableFile { // Symbols declared in the file. std::vector declared_symbols; // Symbols which have definitions in the file. @@ -37,7 +37,7 @@ struct QueryableEntry { // The query database is heavily optimized for fast queries. It is stored // in-memory. -struct QueryDatabase { +struct QueryableDatabase { // Indicies between lookup vectors are related to symbols, ie, index 5 in // |qualified_names| matches index 5 in |symbols|. std::vector qualified_names; @@ -51,7 +51,7 @@ struct QueryDatabase { // |files| is indexed by FileId. Retrieve a FileId from a path using // |file_locator|. FileDatabase file_locator; - std::vector files; + std::vector files; }; @@ -91,27 +91,45 @@ struct Query { }; -void fail(const std::string& message) { - std::cerr << "Fatal error: " << message << std::endl; - std::exit(1); -} +struct CachedIndexedFile { + // Path to the file indexed. + std::string path; + + // Full in-memory storage for the index. Empty if not loaded into memory. + // |path| can be used to fetch the index from disk. + optional index; +}; + +struct DocumentDiff { + +}; +// Compute a diff between |original| and |updated|. +//rapidjson::Document DiffIndex(rapidjson::Document original, rapidjson::Document updated) { + +//} + + + +// NOTE: If updating this enum, make sure to also update the parser and the +// help text. enum class PreferredSymbolLocation { Declaration, Definition }; -std::istream& operator >> (std::istream& is, PreferredSymbolLocation& obj) { - std::string content; - is >> content; - if (content == "declaration") - obj = PreferredSymbolLocation::Declaration; - else if (content == "definition") - obj = PreferredSymbolLocation::Definition; - else - is.setstate(std::ios::failbit); +bool ParsePreferredSymbolLocation(const std::string& content, PreferredSymbolLocation* obj) { +#define PARSE_AS(name, string) \ + if (content == #string) { \ + *obj = name; \ + return true; \ + } - return is; + PARSE_AS(PreferredSymbolLocation::Declaration, "declaration"); + PARSE_AS(PreferredSymbolLocation::Definition, "definition"); + + return false; +#undef PARSE_AS } // NOTE: If updating this enum, make sure to also update the parser and the @@ -127,34 +145,24 @@ enum class Command { Search }; -//std::ostream& operator<<(std::ostream& os, const Command& obj) { - // write obj to stream -// return os; -//} -std::istream& operator >> (std::istream& is, Command& obj) { - std::string content; - is >> content; +bool ParseCommand(const std::string& content, Command* obj) { +#define PARSE_AS(name, string) \ + if (content == #string) { \ + *obj = name; \ + return true; \ + } - if (content == "callees") - obj = Command::Callees; - else if (content == "callers") - obj = Command::Callers; - else if (content == "find-all-usages") - obj = Command::FindAllUsages; - else if (content == "find-interesting-usages") - obj = Command::FindInterestingUsages; - else if (content == "goto-referenced") - obj = Command::GotoReferenced; - else if (content == "hierarchy") - obj = Command::Hierarchy; - else if (content == "outline") - obj = Command::Outline; - else if (content == "search") - obj = Command::Search; - else - is.setstate(std::ios::failbit); + PARSE_AS(Command::Callees, "callees"); + PARSE_AS(Command::Callers, "callers"); + PARSE_AS(Command::FindAllUsages, "find-all-usages"); + PARSE_AS(Command::FindInterestingUsages, "find-interesting-usages"); + PARSE_AS(Command::GotoReferenced, "goto-referenced"); + PARSE_AS(Command::Hierarchy, "hierarchy"); + PARSE_AS(Command::Outline, "outline"); + PARSE_AS(Command::Search, "search"); - return is; + return false; +#undef PARSE_AS } // TODO: I think we can run libclang multiple times in one process. So we might @@ -196,7 +204,7 @@ bool HasOption(const std::unordered_map& options, cons return options.find(option) != options.end(); } -int main(int argc, char** argv) { +int main2(int argc, char** argv) { std::unordered_map options = ParseOptions(argc, argv); if (argc == 1 || options.find("--help") != options.end()) { @@ -283,10 +291,10 @@ int main(int argc, char** argv) { std::vector entries = LoadCompilationEntriesFromDirectory(options["--project"]); - std::vector dbs; + std::vector dbs; for (const CompilationEntry& entry : entries) { std::cout << "Parsing " << entry.filename << std::endl; - ParsingDatabase db = Parse(entry.filename, entry.args); + IndexedFile db = Parse(entry.filename, entry.args); dbs.emplace_back(db); std::cout << db.ToString() << std::endl << std::endl; @@ -296,6 +304,13 @@ int main(int argc, char** argv) { exit(0); } + if (HasOption(options, "--command")) { + Command command; + if (!ParseCommand(options["--command"], &command)) + Fail("Unknown command \"" + options["--command"] + "\"; see --help-commands"); + + + } std::cout << "Invalid arguments. Try --help."; exit(1); diff --git a/utils.cc b/utils.cc index fd5ccab3..a73325d7 100644 --- a/utils.cc +++ b/utils.cc @@ -1,5 +1,6 @@ #include "utils.h" +#include #include #include "tinydir.h" @@ -66,3 +67,9 @@ void ParseTestExpectation(std::string filename, std::string* expected_output) { in_output = true; } } + + +void Fail(const std::string& message) { + std::cerr << "Fatal error: " << message << std::endl; + std::exit(1); +} \ No newline at end of file diff --git a/utils.h b/utils.h index 3fe81ad9..79bff3de 100644 --- a/utils.h +++ b/utils.h @@ -5,4 +5,6 @@ std::vector GetFilesInFolder(std::string folder); std::vector ReadLines(std::string filename); -void ParseTestExpectation(std::string filename, std::string* expected_output); \ No newline at end of file +void ParseTestExpectation(std::string filename, std::string* expected_output); + +void Fail(const std::string& message); \ No newline at end of file