Skip to content

Commit

Permalink
Formalize grammar
Browse files Browse the repository at this point in the history
Replace the parser with a PEG.js AST parser and an AST evaluator.

ref #21
  • Loading branch information
frostburn committed Nov 27, 2023
1 parent 7f796ae commit c0842ac
Show file tree
Hide file tree
Showing 10 changed files with 517 additions and 368 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist/
docs/
src/sw2-ast.js
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,7 @@ dist
# Typedoc

docs/

# Generated grammars

src/sw2-ast.js
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"scripts": {
"lint": "gts lint",
"clean": "gts clean",
"compile-parser": "peggy src/sw2.pegjs -o src/sw2-ast.js",
"precompile": "npm run compile-parser",
"compile": "tsc",
"fix": "gts fix",
"prepare": "npm run compile",
Expand All @@ -46,6 +48,7 @@
"doc": "typedoc --entryPointStrategy packages . --name scale-workshop-core"
},
"dependencies": {
"peggy": "^3.0.2",
"xen-dev-utils": "^0.1.4"
}
}
14 changes: 12 additions & 2 deletions src/__tests__/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ describe('Line parser', () => {
).toThrow();
});

it('rejects fractions without a numerator', () => {
expect(() => parseLine('/5')).toThrow();
});

it('parses N-of-EDO (negative)', () => {
const result = parseLine('-2\\5');
expect(
Expand Down Expand Up @@ -114,6 +118,10 @@ describe('Line parser', () => {
expect(result.type).toBe('equal temperament');
});

it('rejects N-of-EDO without a numerator', () => {
expect(() => parseLine('\\8')).toThrow();
});

it('parses monzos', () => {
const result = parseLine('[-1, 2, 3/2, 0>');
const components = [new Fraction(-1), new Fraction(2), new Fraction(3, 2)];
Expand All @@ -127,7 +135,8 @@ describe('Line parser', () => {
expect(result.type).toBe('monzo');
});

it('parses composites (positive offset)', () => {
// Skipped because disappearing cents have mostly caused confusion
it.skip('parses composites (positive offset)', () => {
const result = parseLine('3\\5 + 5.');
expect(result.monzo.cents).toBeCloseTo(5);
expect(result.name).toBe('3\\5');
Expand All @@ -144,7 +153,8 @@ describe('Line parser', () => {
expect(result.equals(expected)).toBeTruthy();
});

it('parses composites (negative offset)', () => {
// See above for why this is skipped
it.skip('parses composites (negative offset)', () => {
const result = parseLine('3/2 - 1.955');
expect(result.monzo.cents).toBeCloseTo(-1.955);
expect(result.name).toBe('3/2');
Expand Down
96 changes: 96 additions & 0 deletions src/__tests__/sw2-ast.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {describe, it, expect} from 'vitest';
import {parse} from '../sw2-ast';

describe('Scale Workshop 2 Abstract Syntax Tree Parser', () => {
it('parses plain numbers as plain literals ', () => {
const ast = parse('81');
expect(ast.type).toBe('PlainLiteral');
expect(ast.value).toBe(81);
});

it('parses dot-separated numbers as cents literals', () => {
const ast = parse('81.80');
expect(ast.type).toBe('CentsLiteral');
expect(ast.whole).toBe(81);
expect(ast.fractional).toBe('80');
});

it('parses comma-separated numbers as numeric literals', () => {
const ast = parse('81,80');
expect(ast.type).toBe('NumericLiteral');
expect(ast.whole).toBe(81);
expect(ast.fractional).toBe('80');
});

it('parses leading zeroes in the fractional part of cents literals', () => {
const ast = parse('.00123');
expect(ast.type).toBe('CentsLiteral');
expect(ast.whole).toBe(null);
expect(ast.fractional).toBe('00123');
});

it('parses leading zeroes in the fractional part of numeric literals', () => {
const ast = parse(',00123');
expect(ast.type).toBe('NumericLiteral');
expect(ast.whole).toBe(null);
expect(ast.fractional).toBe('00123');
});

it('parses slash-separated numbers as fraction literals', () => {
const ast = parse('81/80');
expect(ast.type).toBe('FractionLiteral');
expect(ast.numerator).toBe(81);
expect(ast.denominator).toBe(80);
});

it('parses backslash-separated numbers as EDJI fractions', () => {
const ast = parse('5\\7');
expect(ast.type).toBe('EdjiFraction');
expect(ast.numerator).toBe(5);
expect(ast.denominator).toBe(7);
expect(ast.equave).toBe(null);
});

it('parses EDJI fractions with explicit equaves', () => {
const ast = parse('6\\13<3>');
expect(ast.type).toBe('EdjiFraction');
expect(ast.numerator).toBe(6);
expect(ast.denominator).toBe(13);
expect(ast.equave.type).toBe('PlainLiteral');
expect(ast.equave.value).toBe(3);
});

it('parses space-separated numbers between a square and an angle bracket as monzos', () => {
const ast = parse('[-4 4 -1>');
expect(ast.type).toBe('Monzo');
expect(ast.components).toEqual(['-4', '4', '-1']);
});

it('parses comma-separated numbers between a square and an angle bracket as monzos', () => {
const ast = parse('[-4, 4, -1>');
expect(ast.type).toBe('Monzo');
expect(ast.components).toEqual(['-4', '4', '-1']);
});

it('parses unary negated EDJI', () => {
const ast = parse('-1\\12');
expect(ast.type).toBe('UnaryExpression');
expect(ast.operator).toBe('-');
expect(ast.operand.type).toBe('EdjiFraction');
expect(ast.operand.numerator).toBe(1);
expect(ast.operand.denominator).toBe(12);
expect(ast.operand.equave).toBe(null);
});

it('parses binary added numbers and cents', () => {
const ast = parse('2 + 1.23');
expect(ast.type).toBe('BinaryExpression');
expect(ast.operator).toBe('+');
});

it('parses binary subtracted numbers and cents', () => {
const ast = parse('2 - 1.23');
expect(ast.type).toBe('BinaryExpression');
expect(ast.operator).toBe('-');
});
});
3 changes: 3 additions & 0 deletions src/monzo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ export class ExtendedMonzo {
while (vector.length < numberOfComponents) {
vector.push(new Fraction(0));
}
if (value === 0) {
return new ExtendedMonzo(vector, new Fraction(0), 0);
}
return new ExtendedMonzo(vector, undefined, valueToCents(value));
}

Expand Down
Loading

0 comments on commit c0842ac

Please sign in to comment.