parseLanguageVersion function

  1. @useResult
LanguageVersion parseLanguageVersion(
  1. String source
)

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 of 0.
  • 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);
}