diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Conf.cpp | 336 | ||||
-rw-r--r-- | src/FileUtil.cpp | 25 | ||||
-rw-r--r-- | src/main.cpp | 84 |
3 files changed, 424 insertions, 21 deletions
diff --git a/src/Conf.cpp b/src/Conf.cpp index d535c60..cea1b01 100644 --- a/src/Conf.cpp +++ b/src/Conf.cpp @@ -1,18 +1,342 @@ #include "../include/Conf.hpp" #include "../include/FileUtil.hpp" +#include "../external/utf8/unchecked.h" +#include <stdexcept> using namespace std; +using u8string = utf8::unchecked::iterator<char*>; namespace sibs { - Result<string> readConf(const char *filepath) + u32 min(u32 a, u32 b) { return a < b ? a : b; } + + class UnexpectedTokenException : public std::runtime_error + { + public: + UnexpectedTokenException(const string &errMsg) : runtime_error(errMsg) + { + + } + }; + + enum class Token + { + NONE, + END_OF_FILE, + IDENTIFIER, + OPEN_BRACKET, + CLOSING_BRACKET, + EQUALS, + STRING + }; + + const char *getTokenName(Token token) + { + switch(token) + { + case Token::NONE: return "NONE"; + case Token::END_OF_FILE: return "<EOF>"; + case Token::IDENTIFIER: return "identifier"; + case Token::OPEN_BRACKET: return "["; + case Token::CLOSING_BRACKET: return "]"; + case Token::EQUALS: return "="; + case Token::STRING: return "string"; + default: return "Unknown"; + } + } + + class Tokenizer + { + public: + Tokenizer(const char *_code) : + currentToken(Token::NONE), + code((char*)_code) + { + + } + + Token nextToken() + { + u32 c = *code; + while(isWhitespace(c)) + { + ++code; + c = *code; + } + + if(isAlpha(c) || c == '_') + { + char *startOfIdentifier = code.base(); + ++code; + c = *code; + while(isAlpha(c) || c == '_' || isDigit(c)) + { + ++code; + c = *code; + } + char *endOfIdentifier = code.base(); + + identifier = StringView(startOfIdentifier, endOfIdentifier - startOfIdentifier); + return Token::IDENTIFIER; + } + else if(c == '[') + { + ++code; + return Token::OPEN_BRACKET; + } + else if(c == ']') + { + ++code; + return Token::CLOSING_BRACKET; + } + else if(c == '=') + { + ++code; + return Token::EQUALS; + } + else if(c == '"') + { + u32 escapeCount = 0; + + ++code; + char *startOfStr = code.base(); + while(escapeCount > 0 || *code != '"') + { + c = *code; + if(c == '\0') + return Token::END_OF_FILE; + else if(c == '\\') + ++escapeCount; + else + escapeCount = min(0, escapeCount - 1); + ++code; + } + + str = StringView(startOfStr, code.base() - startOfStr); + ++code; + return Token::STRING; + } + else if(c == '\0') + { + return Token::END_OF_FILE; + } + else + { + string errMsg = "Unexpected token: "; + errMsg += (char)c; + throw UnexpectedTokenException(errMsg); + } + } + + StringView getIdentifier() const { return identifier; } + StringView getString() const { return str; } + private: + bool isWhitespace(u32 c) + { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + bool isAlpha(u32 c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + bool isDigit(u32 c) + { + return c >= '0' && c <= '9'; + } + private: + Token currentToken; + u8string code; + + union + { + StringView identifier; + StringView str; + }; + }; + + class ParserException : public std::runtime_error { - Result<string> fileContentResult = getFileContent(filepath); + public: + ParserException(const string &errMsg) : runtime_error(errMsg) + { + + } + }; + + class Parser + { + public: + static Result<bool> parse(const char *code, const ConfigCallback &callback) + { + try + { + Parser parser(code, (ConfigCallback*)&callback); + parser.parse(); + return Result<bool>::Ok(true); + } + catch (const UnexpectedTokenException &e) + { + return Result<bool>::Err(e.what()); + } + catch (const ParserException &e) + { + return Result<bool>::Err(e.what()); + } + } + private: + Parser(const char *code, ConfigCallback *_callback) : + tokenizer(code), + callback(_callback), + objectDefined(false) + { + + } + + void parse() + { + while(true) + { + Token token = tokenizer.nextToken(); + switch(token) + { + case Token::IDENTIFIER: + { + parseConfigField(); + break; + } + case Token::OPEN_BRACKET: + { + parseConfigObject(); + break; + } + case Token::END_OF_FILE: + { + callback->finished(); + return; + } + default: + { + string errMsg = "Expected identifier or object, got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + } + } + + void parseConfigField() + { + StringView fieldName = tokenizer.getIdentifier(); + if (!objectDefined) + { + string errMsg = "An object has to be the first element defined in a config file"; + throw ParserException(errMsg); + } + + Token token = tokenizer.nextToken(); + if(token == Token::EQUALS) + { + parseConfigFieldRhs(fieldName); + } + else + { + string errMsg = "Expected '=' after identifier, got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + + void parseConfigFieldRhs(StringView fieldName) + { + Token token = tokenizer.nextToken(); + if(token == Token::STRING) + { + callback->processField(fieldName, tokenizer.getString()); + } + else if(token == Token::OPEN_BRACKET) + { + token = tokenizer.nextToken(); + if(token == Token::STRING) + { + StringView str = tokenizer.getString(); + token = tokenizer.nextToken(); + if(token == Token::CLOSING_BRACKET) + { + vector<StringView> values; + values.push_back(str); + callback->processField(fieldName, values); + } + else + { + string errMsg = "Expected ']' to close value list, got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + else + { + string errMsg = "Expected string value inside list in field definition, got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + else + { + string errMsg = "Expected string on right-hand side of field '"; + errMsg += string(fieldName.data, fieldName.size); + errMsg += "', got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + + void parseConfigObject() + { + Token token = tokenizer.nextToken(); + if(token == Token::IDENTIFIER) + { + StringView objectName = tokenizer.getIdentifier(); + token = tokenizer.nextToken(); + if(token == Token::CLOSING_BRACKET) + { + objectDefined = true; + callback->processObject(objectName); + } + else + { + string errMsg = "Expected ']' after identifier to close object definition, got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + else + { + string errMsg = "Expected identifier after '[', got: "; + errMsg += getTokenName(token); + throw ParserException(errMsg); + } + } + private: + Tokenizer tokenizer; + ConfigCallback *callback; + bool objectDefined; + }; + + Result<bool> Config::readFromFile(const char *filepath, const ConfigCallback &callback) + { + Result<StringView> fileContentResult = getFileContent(filepath); if(fileContentResult.isErr()) - return fileContentResult; + return Result<bool>::Err(fileContentResult.getErrMsg()); + + const char *code = fileContentResult.unwrap().data; + if(!utf8::is_valid(code, code + fileContentResult.unwrap().size)) + return Result<bool>::Err("File is not in valid utf8 format"); + + if(fileContentResult.unwrap().size >= 3 && utf8::is_bom(code)) + code += 3; - string configData; - printf("file content:\n%s\n", fileContentResult.unwrap().c_str()); - return Result<string>::Ok(std::move(configData)); + return Parser::parse(code, callback); } }
\ No newline at end of file diff --git a/src/FileUtil.cpp b/src/FileUtil.cpp index 511a324..30fe03d 100644 --- a/src/FileUtil.cpp +++ b/src/FileUtil.cpp @@ -18,7 +18,7 @@ namespace sibs } } - void walkDirectory(const char *directory, FileWalkCallbackFunc callbackFunc) + void walkDirFiles(const char *directory, FileWalkCallbackFunc callbackFunc) { tinydir_dir dir; tinydir_open(&dir, directory); @@ -27,30 +27,39 @@ namespace sibs { tinydir_file file; tinydir_readfile(&dir, &file); - callbackFunc(&file); + if(file.is_reg) + callbackFunc(&file); + else if(_tinydir_strcmp(file.name, ".") != 0 && _tinydir_strcmp(file.name, "..") != 0) + walkDirFiles(file.path, callbackFunc); tinydir_next(&dir); } tinydir_close(&dir); } - Result<string> getFileContent(const char *filepath) + Result<StringView> getFileContent(const char *filepath) { FILE *file = fopen(filepath, "rb"); if(!file || errno != 0) { perror(filepath); - return Result<string>::Err("Failed to open file"); + return Result<StringView>::Err("Failed to open file"); } fseek(file, 0, SEEK_END); size_t fileSize = ftell(file); fseek(file, 0, SEEK_SET); - string result; - result.resize(fileSize); - fread(&result[0], 1, fileSize, file); + char *result = (char*)malloc(fileSize + 1); + if(!result) + { + std::string errMsg = "Failed to load file content from file: "; + errMsg += filepath; + throw std::runtime_error(errMsg); + } + result[fileSize] = '\0'; + fread(result, 1, fileSize, file); fclose(file); - return Result<string>::Ok(std::move(result)); + return Result<StringView>::Ok(StringView(result, fileSize)); } }
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d49ae1f..ce914b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ void usage() exit(1); } -void validateProjectPath(const char *projectPath) +void validateDirectoryPath(const char *projectPath) { FileType projectPathFileType = getFileType(projectPath); if(projectPathFileType == FileType::FILE_NOT_FOUND) @@ -33,7 +33,7 @@ void validateProjectPath(const char *projectPath) } } -void validateProjectConfPath(const char *projectConfPath) +void validateFilePath(const char *projectConfPath) { FileType projectConfFileType = getFileType(projectConfPath); if(projectConfFileType == FileType::FILE_NOT_FOUND) @@ -43,11 +43,66 @@ void validateProjectConfPath(const char *projectConfPath) } else if(projectConfFileType == FileType::DIRECTORY) { - printf("Expected project conf (%s) to be a directory, was a file", projectConfPath); + printf("Expected project path (%s) to be a file, was a directory", projectConfPath); exit(5); } } +class SibsConfig : public ConfigCallback +{ +protected: + void processObject(StringView name) override + { + currentObject = name; + printf("Process object: %.*s\n", name.size, name.data); + } + + void processField(StringView name, const ConfigValue &value) override + { + printf("Process field: %.*s, value: ", name.size, name.data); + if(value.isSingle()) + { + printf("\"%.*s\"", value.asSingle().size, value.asSingle().data); + } + else + { + printf("["); + int i = 0; + for(auto listElement : value.asList()) + { + if(i > 0) + printf(", "); + printf("\"%.*s\"", listElement.size, listElement.data); + ++i; + } + printf("]"); + } + printf("\n"); + } + + void finished() override + { + + } + +private: + StringView currentObject; +}; + +const char *sourceFileExtensions[] = { "cc", "cpp", "cxx" }; +bool isSourceFile(tinydir_file *file) +{ + if(!file->is_reg) + return false; + + for(const char *sourceFileExtension : sourceFileExtensions) + { + if(_tinydir_strcmp(sourceFileExtension, file->extension) == 0) + return true; + } + + return false; +} int main(int argc, const char **argv) { @@ -55,22 +110,37 @@ int main(int argc, const char **argv) usage(); const char *projectPath = argv[1]; - validateProjectPath(projectPath); + validateDirectoryPath(projectPath); string projectConfFilePath = projectPath; projectConfFilePath += "/project.conf"; - validateProjectConfPath(projectConfFilePath.c_str()); + validateFilePath(projectConfFilePath.c_str()); - auto result = readConf(projectConfFilePath.c_str()); + SibsConfig sibsConfig; + Result<bool> result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if(result.isOk()) { } else { - printf("Failed to read config: %s\n", result.getErrMsg()); + printf("Failed to read config: %s\n", result.getErrMsg().c_str()); exit(6); } + string projectSrcPath = string(projectPath) + "/src"; + validateDirectoryPath(projectSrcPath.c_str()); + walkDirFiles(projectSrcPath.c_str(), [](tinydir_file *file) + { + if (isSourceFile(file)) + { + printf("source file: %s\n", file->path); + } + else + { + printf("non source file: %s\n", file->path); + } + }); + return 0; }
\ No newline at end of file |