#include "../include/VersionParser.hpp" #include "../include/StringView.hpp" namespace sibs { static int stringToIntNoVerify(const StringView &str) { int result = 0; for(usize i = 0; i < str.size; ++i) { result = (result * 10) + (str[i] - '0'); } 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"); if(versionRange.startOperation == VersionOperation::NONE) { versionRange.startOperation = VersionOperation::GREATER_EQUAL; versionRange.end.major = versionRange.start.major + 1; versionRange.end.minor = 0; versionRange.end.patch = 0; versionRange.endOperation = VersionOperation::LESS; versionRange.endDefined = true; } 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::NONE; 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; } }