diff --git a/src/__tests__/cli.spec.ts b/src/__tests__/cli.spec.ts index cb211ef8..93fd0569 100644 --- a/src/__tests__/cli.spec.ts +++ b/src/__tests__/cli.spec.ts @@ -17,9 +17,9 @@ describe('Interchange format', () => { expect(result).toContain('nan'); }); - it('has representation for infinity Hz', () => { - const result = toSonicWeaveInterchange('inf * 1 Hz'); - expect(result).toContain('inf * 1Hz'); + it('has representation for negative infinity Hz', () => { + const result = toSonicWeaveInterchange('-inf * 1 Hz'); + expect(result).toContain('[1. 1 1>@Hz.-1.inf'); }); it('has representation for nan Hz (normalizes)', () => { diff --git a/src/expression.ts b/src/expression.ts index 02d90c9d..1ab228b5 100644 --- a/src/expression.ts +++ b/src/expression.ts @@ -74,7 +74,7 @@ export type WartBasisElement = BasisFraction | ''; export type ValBasisElement = WartBasisElement | 's' | 'Hz' | 'hz'; -export type BasisElement = ValBasisElement | 'rc' | 'r¢' | '1°'; +export type BasisElement = ValBasisElement | 'rc' | 'r¢' | 'inf' | '1°'; export type IntegerLiteral = { type: 'IntegerLiteral'; diff --git a/src/grammars/base.pegjs b/src/grammars/base.pegjs index 57ab9cca..1f6c7b29 100644 --- a/src/grammars/base.pegjs +++ b/src/grammars/base.pegjs @@ -246,7 +246,7 @@ SecondToken = @'s' !IdentifierPart ValBasisElement = Fraction / SecondToken / HertzToken / LowHertzToken -BasisElement = ValBasisElement / RealCentToken / 'r¢' / '1°' / '' +BasisElement = ValBasisElement / RealCentToken / 'r¢' / 'inf' / '1°' / '' ValBasis = (ValBasisElement / '')|.., '.'| diff --git a/src/monzo.ts b/src/monzo.ts index d9154aa7..2736ce9c 100644 --- a/src/monzo.ts +++ b/src/monzo.ts @@ -746,7 +746,7 @@ export class TimeReal { * @returns Monzo literal. */ asMonzoLiteral(interchange = false): MonzoLiteral | undefined { - if (!isFinite(this.value)) { + if (isNaN(this.value)) { return undefined; } const components: VectorComponent[] = []; @@ -801,6 +801,11 @@ export class TimeReal { }); return {type: 'MonzoLiteral', components, ups: 0, lifts: 0, basis}; } + if (!isFinite(this.value)) { + basis.push('inf'); + components.push({sign: '', left: 1, right: '', exponent: null}); + return {type: 'MonzoLiteral', components, ups: 0, lifts: 0, basis}; + } basis.push('rc'); const {sign, whole, fractional, exponent} = numberToDecimalLiteral( this.totalCents(true), diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index 9ccc65b5..797490c7 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -2397,4 +2397,10 @@ describe('Poor grammar / Fun with "<"', () => { expect(interval.isAbsolute()).toBe(false); expect(interval.valueOf()).toBeCloseTo(Math.PI); }); + + it('parses universal negative infinite frequency', () => { + const interval = evaluate('[1. 1 1>@Hz.-1.inf') as Interval; + expect(interval.isAbsolute()).toBe(true); + expect(interval.valueOf()).toBe(-Infinity); + }); }); diff --git a/src/warts.ts b/src/warts.ts index 9b5037f2..14c7f10c 100644 --- a/src/warts.ts +++ b/src/warts.ts @@ -12,6 +12,7 @@ const TWO_MONZO = new TimeMonzo(ZERO, [ONE]); const SECOND_MONZO = new TimeMonzo(ONE, []); const HERTZ_MONZO = new TimeMonzo(NEGATIVE_ONE, []); const REAL_CENT_MONZO = new TimeReal(0, 1.0005777895065548); +const INF_MONZO = new TimeReal(0, Infinity); export const STEP_ELEMENT = Symbol(); @@ -57,6 +58,8 @@ export function parseSubgroup(basis: BasisElement[], targetSize?: number) { } else if (element === 'rc' || element === 'r¢') { subgroup.push(REAL_CENT_MONZO); checkSpan(); + } else if (element === 'inf') { + subgroup.push(INF_MONZO); } else if (element === '1°') { subgroup.push(STEP_ELEMENT); checkSpan();