aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Conf.cpp336
-rw-r--r--src/FileUtil.cpp25
-rw-r--r--src/main.cpp84
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