From 6bb79ef033c2a2e8f12c9da6409e3547af40417c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 18 Oct 2018 07:05:43 +0200 Subject: Use ranges for dependency version --- src/VersionParser.cpp | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/VersionParser.cpp (limited to 'src/VersionParser.cpp') diff --git a/src/VersionParser.cpp b/src/VersionParser.cpp new file mode 100644 index 0000000..7b28cef --- /dev/null +++ b/src/VersionParser.cpp @@ -0,0 +1,362 @@ +#include "../include/VersionParser.hpp" +#include "../include/StringView.hpp" + +namespace sibs +{ + static int stringToIntNoVerify(const StringView &str) + { + int result = 0; + if(str.size > 0) + result += (str[0] - '0'); + + for(int i = 1; i < (int)str.size; ++i) + { + int num = str[i] - '0'; + result += (10 * ((int)str.size - i) * num); + } + return result; + } + + static bool isNum(char c) + { + return c >= '0' && c <= '9'; + } + + static bool isAlpha(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + Result parsePackageVersion(const StringView &versionStr, int *size) + { + PackageVersion result = { 0, 0, 0 }; + int *versionPtr = &result.major; + int digitStart = 0; + int i = 0; + + for(; i < (int)versionStr.size; ++i) + { + char c = versionStr[i]; + if(isNum(c)) + { + if(digitStart == -1) + digitStart = i; + } + else + { + int length = i - digitStart; + if(digitStart == -1 || length == 0) + return Result::Err("Package version string is in invalid format. Expected to be in format xxx.xxx.xxx where x is a number"); + + *versionPtr = stringToIntNoVerify({ versionStr.data + digitStart, (usize)(i - digitStart) }); + bool endOfVersions = versionPtr == &result.patch; + ++versionPtr; + digitStart = -1; + + if(c != '.' || endOfVersions) + break; + } + } + + if(i == 0) + return Result::Err("version can't be empty"); + + if(digitStart != -1) + { + *versionPtr = stringToIntNoVerify({ versionStr.data + digitStart, (usize)(i - digitStart) }); + ++versionPtr; + } + + if(size) + *size = i; + + if(versionPtr == &result.major) + return Result::Err("version can't be empty"); + + return Result::Ok(result); + } + + VersionTokenizer::VersionTokenizer() : + start(nullptr), + code(nullptr), + size(0), + index(0) + { + + } + + VersionTokenizer::VersionTokenizer(const char *_start, const usize _size) : + start(_start), + code(_start), + size(_size), + index(0) + { + + } + + VersionTokenizer::VersionTokenizer(const VersionTokenizer &other) + { + start = other.start; + code = other.code; + size = other.size; + index = other.index; + } + + VersionToken VersionTokenizer::next() + { + while(index < size) + { + char c = code[index]; + if(c == ' ' || c == '\t' || c == '\n' || c == '\r') + ++index; + else + break; + } + + if(index >= size) + return VersionToken::END_OF_FILE; + + char c = code[index]; + if(isNum(c)) + { + int versionStrSize = 0; + identifier.data = code + index; + Result packageVersion = parsePackageVersion({ code + index, (usize)(size - index) }, &versionStrSize); + identifier.size = versionStrSize; + index += versionStrSize; + if(!packageVersion) + { + errMsg = packageVersion.getErrMsg(); + return VersionToken::INVALID; + } + version = packageVersion.unwrap(); + return VersionToken::VERSION_NUMBER; + } + else if(isAlpha(c)) + { + usize identifierStart = index; + ++index; + while(index < size && isAlpha(code[index])) + { + ++index; + } + usize identifierEnd = index; + usize identifierLength = identifierEnd - identifierStart; + + if(identifierLength == 3 && strncmp(code + identifierStart, "and", 3) == 0) + { + return VersionToken::AND; + } + else + { + errMsg = "Invalid identifier "; + errMsg += std::string(code + identifierStart, identifierLength); + return VersionToken::INVALID; + } + } + else if(c == '<') + { + ++index; + if(index < size && code[index] == '=') + { + ++index; + operation = VersionOperation::LESS_EQUAL; + return VersionToken::OPERATION; + } + operation = VersionOperation::LESS; + return VersionToken::OPERATION; + } + else if(c == '=') + { + ++index; + operation = VersionOperation::EQUAL; + return VersionToken::OPERATION; + } + else if(c == '>') + { + ++index; + if(index < size && code[index] == '=') + { + ++index; + operation = VersionOperation::GREATER_EQUAL; + return VersionToken::OPERATION; + } + operation = VersionOperation::GREATER; + return VersionToken::OPERATION; + } + else + { + errMsg = "Unexpected character "; + errMsg += c; + return VersionToken::INVALID; + } + } + + Result VersionParser::parse(const char *code, const usize size) + { + versionRange = PackageVersionRange(); + tokenizer = VersionTokenizer(code, size); + VersionToken token = parseStart(); + + if(token == VersionToken::END_OF_FILE) + { + if(!versionRange.startDefined) + return Result::Err("version can't be empty"); + return Result::Ok(versionRange); + } + else if(token == VersionToken::INVALID) + return Result::Err(tokenizer.errMsg); + else + { + std::string errMsg = "Unexpected token '"; + switch(token) + { + case VersionToken::NONE: + { + errMsg += ""; + break; + } + case VersionToken::OPERATION: + { + errMsg += "operation "; + errMsg += asString(tokenizer.operation); + break; + } + case VersionToken::AND: + { + errMsg += "and"; + break; + } + case VersionToken::VERSION_NUMBER: + { + errMsg += "version "; + errMsg += std::string(tokenizer.identifier.data, tokenizer.identifier.size); + break; + } + default: + break; + } + errMsg += "'"; + return Result::Err(errMsg); + } + } + + VersionToken VersionParser::parseStart() + { + VersionToken token = tokenizer.next(); + if(token == VersionToken::VERSION_NUMBER) + { + versionRange.startOperation = VersionOperation::GREATER_EQUAL; + versionRange.start = tokenizer.version; + versionRange.startDefined = true; + token = tokenizer.next(); + if(token == VersionToken::AND) + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Unexpected end version when start version does not have operation defined"; + } + } + else if(token == VersionToken::OPERATION) + { + versionRange.startOperation = tokenizer.operation; + token = tokenizer.next(); + if(token == VersionToken::VERSION_NUMBER) + { + versionRange.start = tokenizer.version; + versionRange.startDefined = true; + switch(versionRange.startOperation) + { + case VersionOperation::LESS: + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Unexpected version end when start version is expected to less than "; + tokenizer.errMsg += versionRange.start.toString(); + return token; + } + case VersionOperation::LESS_EQUAL: + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Unexpected version end when start version is expected to be less or equal to "; + tokenizer.errMsg += versionRange.start.toString(); + return token; + } + case VersionOperation::EQUAL: + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Unexpected version end when start version is expected to be exactly "; + tokenizer.errMsg += versionRange.start.toString(); + return token; + } + default: + break; + } + + token = tokenizer.next(); + if(token == VersionToken::AND) + { + return parseEnd(); + } + } + else if(token == VersionToken::INVALID) + return token; + else + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Expected version after operation"; + } + } + return token; + } + + VersionToken VersionParser::parseEnd() + { + VersionToken token = tokenizer.next(); + if(token == VersionToken::OPERATION) + { + versionRange.endOperation = tokenizer.operation; + switch(versionRange.endOperation) + { + case VersionOperation::EQUAL: + case VersionOperation::GREATER: + case VersionOperation::GREATER_EQUAL: + { + token = VersionToken::INVALID; + tokenizer.errMsg = "End version can only have operations < and <="; + return token; + } + default: + break; + } + + token = tokenizer.next(); + if(token == VersionToken::VERSION_NUMBER) + { + versionRange.end = tokenizer.version; + versionRange.endDefined = true; + if(versionRange.end <= versionRange.start) + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Expected version end to be greater than "; + tokenizer.errMsg += versionRange.start.toString(); + tokenizer.errMsg += ", was "; + tokenizer.errMsg += versionRange.end.toString(); + return token; + } + token = tokenizer.next(); + } + else if(token == VersionToken::INVALID) + return token; + else + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Expected version after operation"; + } + } + else + { + token = VersionToken::INVALID; + tokenizer.errMsg = "Expected end version to have operation defined"; + } + return token; + } +} -- cgit v1.2.3