Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(interval): ♻️ extract parse into IntervalNotation #416

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 96 additions & 13 deletions lib/src/interval/interval.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ final class Interval
static const A13 =
Interval.imperfect(Size.thirteenth, ImperfectQuality.augmented);

static final _regExp = RegExp(r'(\w+?)(-?\d+)');

/// Creates a new [Interval] allowing only perfect quality [size]s.
const Interval.perfect(
this.size, [
Expand Down Expand Up @@ -207,16 +205,11 @@ final class Interval
/// Interval.parse('P-5') == -Interval.P5
/// Interval.parse('z') // throws a FormatException
/// ```
factory Interval.parse(String source) {
final match = _regExp.firstMatch(source);
if (match == null) throw FormatException('Invalid Interval', source);

final size = Size(int.parse(match[2]!));
final parseFactory =
size.isPerfect ? PerfectQuality.parse : ImperfectQuality.parse;

return Interval._(size, parseFactory(match[1]!));
}
factory Interval.parse(
String source, {
IntervalNotation system = IntervalNotation.standard,
}) =>
system.parseInterval(source);

/// The number of semitones of this [Interval].
///
Expand Down Expand Up @@ -502,18 +495,54 @@ abstract class IntervalNotation {
/// The string notation for [interval].
String interval(Interval interval);

/// Parse [source] as an [Interval].
Interval parseInterval(String source);

/// The string notation for [size].
String size(Size size);

/// Parse [source] as a [Size].
Size parseSize(String source);

/// The string notation for [quality].
String quality(Quality quality);

/// Parse [source] as a [PerfectQuality].
PerfectQuality parsePerfectQuality(String source);

/// Parse [source] as an [ImperfectQuality].
ImperfectQuality parseImperfectQuality(String source);
}

/// The standard [Interval] notation system.
final class StandardIntervalNotation extends IntervalNotation {
/// Creates a new [StandardIntervalNotation].
const StandardIntervalNotation();

static final _intervalRegExp = RegExp(r'(\w+?)(-?\d+)');

/// The symbol for a diminished [Quality].
static const diminishedSymbol = 'd';

/// The symbol for a [PerfectQuality].
static const perfectSymbol = 'P';

/// The symbol for an augmented [Quality].
static const augmentedSymbol = 'A';

/// The symbol for a minor [ImperfectQuality].
static const minorSymbol = 'm';

/// The symbol for a major [ImperfectQuality].
static const majorSymbol = 'M';

static final _perfectQualityRegExp =
RegExp('^($diminishedSymbol+|$perfectSymbol|$augmentedSymbol+)\$');

static final _imperfectQualityRegExp = RegExp(
'^($diminishedSymbol+|$minorSymbol|$majorSymbol|$augmentedSymbol+)\$',
);

@override
String interval(Interval interval) {
final quality = interval.quality.toString(system: this);
Expand All @@ -523,9 +552,63 @@ final class StandardIntervalNotation extends IntervalNotation {
return '$naming ($quality${interval.simple.size.format(system: this)})';
}

@override
Interval parseInterval(String source) {
final match = _intervalRegExp.firstMatch(source);
if (match == null) throw FormatException('Invalid Interval', source);

final size = parseSize(match[2]!);
final parseFactory =
size.isPerfect ? parsePerfectQuality : parseImperfectQuality;

return Interval._(size, parseFactory(match[1]!));
}

@override
String size(Size size) => '$size';

@override
String quality(Quality quality) => quality.symbol;
Size parseSize(String source) => Size(int.parse(source));

@override
String quality(Quality quality) => switch (quality) {
PerfectQuality() => switch (quality.semitones) {
< 0 => diminishedSymbol * quality.semitones.abs(),
0 => perfectSymbol,
_ => augmentedSymbol * quality.semitones,
},
ImperfectQuality() => switch (quality.semitones) {
< 0 => diminishedSymbol * quality.semitones.abs(),
0 => minorSymbol,
1 => majorSymbol,
_ => augmentedSymbol * (quality.semitones - 1),
},
};

@override
PerfectQuality parsePerfectQuality(String source) {
if (!_perfectQualityRegExp.hasMatch(source)) {
throw FormatException('Invalid PerfectQuality', source);
}

return switch (source[0]) {
diminishedSymbol => PerfectQuality(-source.length),
perfectSymbol => PerfectQuality.perfect,
_ /* augmentedSymbol */ => PerfectQuality(source.length),
};
}

@override
ImperfectQuality parseImperfectQuality(String source) {
if (!_imperfectQualityRegExp.hasMatch(source)) {
throw FormatException('Invalid PerfectQuality', source);
}

return switch (source[0]) {
diminishedSymbol => ImperfectQuality(-source.length),
minorSymbol => ImperfectQuality.minor,
majorSymbol => ImperfectQuality.major,
_ /* augmentedSymbol */ => ImperfectQuality(source.length + 1),
};
}
}
86 changes: 10 additions & 76 deletions lib/src/interval/quality.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ sealed class Quality implements Comparable<Quality> {
/// Creates a new [Quality] from [semitones].
const Quality(this.semitones);

static const _diminishedSymbol = 'd';
static const _augmentedSymbol = 'A';

/// The symbol of this [Quality].
String get symbol;

/// The inverted version of this [Quality].
Quality get inverted;

Expand Down Expand Up @@ -95,13 +89,6 @@ final class PerfectQuality extends Quality {
/// A triply augmented [PerfectQuality].
static const triplyAugmented = PerfectQuality(3);

static const _perfectSymbol = 'P';

static final _regExp = RegExp(
'^(${Quality._diminishedSymbol}+|$_perfectSymbol|'
'${Quality._augmentedSymbol}+)\$',
);

/// Parse [source] as a [PerfectQuality] and return its value.
///
/// If the [source] string does not contain a valid [PerfectQuality], a
Expand All @@ -113,32 +100,11 @@ final class PerfectQuality extends Quality {
/// PerfectQuality.parse('dd') == PerfectQuality.doublyDiminished
/// PerfectQuality.parse('z') // throws a FormatException
/// ```
factory PerfectQuality.parse(String source) {
if (!_regExp.hasMatch(source)) {
throw FormatException('Invalid PerfectQuality', source);
}

return switch (source[0]) {
Quality._diminishedSymbol => PerfectQuality(-source.length),
_perfectSymbol => PerfectQuality.perfect,
_ /* Quality._augmentedSymbol */ => PerfectQuality(source.length),
};
}

/// The symbol of this [PerfectQuality].
///
/// Example:
/// ```dart
/// PerfectQuality.perfect.toString() == 'P'
/// PerfectQuality.augmented.toString() == 'A'
/// PerfectQuality.doublyDiminished.toString() == 'dd'
/// ```
@override
String get symbol => switch (semitones) {
< 0 => Quality._diminishedSymbol * semitones.abs(),
0 => _perfectSymbol,
_ => Quality._augmentedSymbol * semitones,
};
factory PerfectQuality.parse(
String source, {
IntervalNotation system = IntervalNotation.standard,
}) =>
system.parsePerfectQuality(source);

/// The inverted version of this [PerfectQuality].
///
Expand Down Expand Up @@ -201,14 +167,6 @@ final class ImperfectQuality extends Quality {
/// A triply augmented [ImperfectQuality].
static const triplyAugmented = ImperfectQuality(4);

static const _minorSymbol = 'm';
static const _majorSymbol = 'M';

static final _regExp = RegExp(
'^(${Quality._diminishedSymbol}+|$_minorSymbol|$_majorSymbol|'
'${Quality._augmentedSymbol}+)\$',
);

/// Parse [source] as a [ImperfectQuality] and return its value.
///
/// If the [source] string does not contain a valid [ImperfectQuality], a
Expand All @@ -220,35 +178,11 @@ final class ImperfectQuality extends Quality {
/// ImperfectQuality.parse('A') == ImperfectQuality.augmented
/// ImperfectQuality.parse('z') // throws a FormatException
/// ```
factory ImperfectQuality.parse(String source) {
if (!_regExp.hasMatch(source)) {
throw FormatException('Invalid PerfectQuality', source);
}

return switch (source[0]) {
Quality._diminishedSymbol => ImperfectQuality(-source.length),
_minorSymbol => ImperfectQuality.minor,
_majorSymbol => ImperfectQuality.major,
_ /* Quality._augmentedSymbol */ => ImperfectQuality(source.length + 1),
};
}

/// The symbol of this [ImperfectQuality].
///
/// Example:
/// ```dart
/// ImperfectQuality.major.toString() == 'M'
/// ImperfectQuality.minor.toString() == 'm'
/// ImperfectQuality.diminished.toString() == 'd'
/// ImperfectQuality.triplyAugmented.toString() == 'AAA'
/// ```
@override
String get symbol => switch (semitones) {
< 0 => Quality._diminishedSymbol * semitones.abs(),
0 => _minorSymbol,
1 => _majorSymbol,
_ => Quality._augmentedSymbol * (semitones - 1),
};
factory ImperfectQuality.parse(
String source, {
IntervalNotation system = IntervalNotation.standard,
}) =>
system.parseImperfectQuality(source);

/// The inverted version of this [ImperfectQuality].
///
Expand Down