parseLanguageVersion function
Parses and returns a LanguageVersion from
the specified source language version string.
Throws an LanguageVersionFormatException exception if
source isn't able to be parsed as a valid language version.
A language version string contains a major version number and
a minor version number separated by a full stop character (.):
<majorVersionNumber>.<minorVersionNumber>
To be considered valid and parse successfully:
- Each version number must be a non-negative, decimal integer.
- Each version number must be within the range of LanguageVersion.minValue and LanguageVersion.maxValue.
- Each version number must not have unnecessary leading zeroes (
0), besides the singular value of0. - There must not be spaces or other characters in the string.
Implementation
@useResult
LanguageVersion parseLanguageVersion(String source) {
if (source.isEmpty) {
throw LanguageVersionFormatException(
'Language version can\'t be empty',
source: source,
offset: 0,
);
}
var sourceIndex = 0;
final majorStart = sourceIndex;
// Verify that the first character of the major version is a digit.
if (sourceIndex >= source.length ||
_isNotDigit(source.codeUnitAt(sourceIndex))) {
throw LanguageVersionFormatException(
'Expected digit at start of major version',
source: source,
offset: sourceIndex,
);
}
// Check if the first major version digit is a 0.
final hasLeadingZero = source.codeUnitAt(sourceIndex) == _digit0;
sourceIndex++;
// Continue reading while there are digits.
while (sourceIndex < source.length &&
_isDigit(source.codeUnitAt(sourceIndex))) {
sourceIndex++;
}
final majorVersionString = source.substring(majorStart, sourceIndex);
// Verify that the major version doesn't have unnecessary leading zeroes.
if (hasLeadingZero && majorVersionString.length > 1) {
throw LanguageVersionFormatException(
'Major version has unnecessary leading zeros',
source: source,
offset: majorStart,
);
}
// Verify that the first character after the
// major version number is a dot (`.`).
if (sourceIndex >= source.length || source.codeUnitAt(sourceIndex) != _dot) {
throw LanguageVersionFormatException(
'Expected "." after major version',
source: source,
offset: sourceIndex,
);
}
// Skip past the dot.
sourceIndex++;
final minorVersionStartIndex = sourceIndex;
// Verify that the first character of the minor version is a digit.
if (sourceIndex >= source.length ||
_isNotDigit(source.codeUnitAt(sourceIndex))) {
throw LanguageVersionFormatException(
'Expected digit at start of minor version',
source: source,
offset: sourceIndex,
);
}
// Check if the first minor version digit is a 0.
final minorVersionHasLeadingZero = source.codeUnitAt(sourceIndex) == _digit0;
sourceIndex++;
// Continue reading while there are digits.
while (sourceIndex < source.length &&
_isDigit(source.codeUnitAt(sourceIndex))) {
sourceIndex++;
}
final minorVersionString = source.substring(
minorVersionStartIndex,
sourceIndex,
);
// Verify that the minor version doesn't have unnecessary leading zeroes.
if (minorVersionHasLeadingZero && minorVersionString.length > 1) {
throw LanguageVersionFormatException(
'Minor version has unnecessary leading zeros',
source: source,
offset: minorVersionStartIndex,
);
}
// Verify that the minor version isn't followed by
// any extra non-digit characters.
if (sourceIndex < source.length) {
throw LanguageVersionFormatException(
'Unexpected character after minor version',
source: source,
offset: sourceIndex,
);
}
// We can assume these parse without error as we would have
// already thrown an error if they weren't integers.
final majorVersion = int.parse(majorVersionString);
final minorVersion = int.parse(minorVersionString);
// Verify that the major version is within the allowed range.
if (majorVersion < LanguageVersion.minValue ||
majorVersion > LanguageVersion.maxValue) {
throw LanguageVersionFormatException(
'Major version must be '
'between ${LanguageVersion.minValue} and ${LanguageVersion.maxValue}',
source: source,
offset: majorStart,
);
}
// Verify that the minor version is within the allowed range.
if (minorVersion < LanguageVersion.minValue ||
minorVersion > LanguageVersion.maxValue) {
throw LanguageVersionFormatException(
'Minor version must be '
'between ${LanguageVersion.minValue} and ${LanguageVersion.maxValue}',
source: source,
offset: minorVersionStartIndex,
);
}
return LanguageVersion(majorVersion, minorVersion);
}