Skip to content

Commit

Permalink
Fix ternary operator associativity
Browse files Browse the repository at this point in the history
Implement some() and every().
Start golfing prelude using the new features.
  • Loading branch information
frostburn committed Apr 29, 2024
1 parent 55ba3de commit 3fc17de
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 34 deletions.
33 changes: 22 additions & 11 deletions src/grammars/sonic-weave.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -572,17 +572,28 @@ LestExpression

ConditionalExpression
= consequent: CoalescingExpression tail: (__ @(IfToken / WhereToken) _ @CoalescingExpression _ ElseToken _ @CoalescingExpression)* {
return tail.reduce(
(result, [kind, test, alternate]) => (
{
type: 'ConditionalExpression',
kind,
test,
alternate,
consequent: result,
}
), consequent
);
if (!tail.length) {
return consequent;
}
const [kind, test, alternate] = tail.pop();
let result = {
type: 'ConditionalExpression',
kind,
test,
alternate,
};
while (tail.length) {
const [kind, test, alternate] = tail.pop();
result.consequent = alternate;
result = {
type: 'ConditionalExpression',
kind,
test,
alternate: result,
};
}
result.consequent = consequent;
return result;
}

CoalescingOperator = '??' / OrToken / VectorOrToken
Expand Down
18 changes: 9 additions & 9 deletions src/parser/__tests__/sonic-weave-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,22 +1010,22 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
});
});

it('has a left-associative ternary operator complex', () => {
it('has a ternary operator complex with pythonic associativity', () => {
const ast = parseSingle('foo if bar else baz where qux else quux');
expect(ast).toEqual({
type: 'ExpressionStatement',
expression: {
type: 'ConditionalExpression',
kind: 'where',
test: {type: 'Identifier', id: 'qux'},
alternate: {type: 'Identifier', id: 'quux'},
consequent: {
kind: 'if',
test: {type: 'Identifier', id: 'bar'},
alternate: {
type: 'ConditionalExpression',
kind: 'if',
test: {type: 'Identifier', id: 'bar'},
alternate: {type: 'Identifier', id: 'baz'},
consequent: {type: 'Identifier', id: 'foo'},
kind: 'where',
test: {type: 'Identifier', id: 'qux'},
alternate: {type: 'Identifier', id: 'quux'},
consequent: {type: 'Identifier', id: 'baz'},
},
consequent: {type: 'Identifier', id: 'foo'},
},
});
});
Expand Down
7 changes: 7 additions & 0 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1373,4 +1373,11 @@ describe('SonicWeave parser', () => {
}`);
expect(scale).toEqual(['0', '4', '5', '6', '7']);
});

it('could golf ablin', () => {
const scale = expand(
'1=6z;{const ablin = i => i linear absolute;3::6;ablin}'
);
expect(scale).toEqual(['8 Hz', '10 Hz', '12 Hz']);
});
});
2 changes: 1 addition & 1 deletion src/parser/__tests__/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ describe('SonicWeave standard library', () => {

it('throws an error if you use reduce as a mapper', () => {
expect(() => parseSource('3/2;4/2;reduce')).toThrow(
'Can only iterate over arrays, records or strings.'
'Can only access arrays, records or strings.'
);
});

Expand Down
14 changes: 14 additions & 0 deletions src/parser/__tests__/vector-broadcasting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,18 @@ describe('SonicWeave vector broadcasting', () => {
expect(vecLR[0].strictEquals(z)).toBe(true);
}
);

it('has a broadcasting sign', () => {
const zero = sw0D`0 sign`;
expect(zero).toBe(0);

const polarity = sw1D`[-1/2, PI] sign`;
expect(polarity).toEqual([-1, 1]);

const circleOfNaN = sw2D`sign([[0, LN2], [-1, NaN]])`;
expect(circleOfNaN).toEqual([
[0, 1],
[-1, NaN],
]);
});
});
38 changes: 38 additions & 0 deletions src/stdlib/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,42 @@ function arrayRepeat(
arrayRepeat.__doc__ = 'Repeat the given/current array or string `count` times.';
arrayRepeat.__node__ = builtinNode(arrayRepeat);

function some(
this: ExpressionVisitor,
array?: any[],
test?: (value: any, index: Interval, array: any[]) => unknown
) {
if (!array) {
array = this.currentScale;
}
this.spendGas(array.length);
if (!test) {
return array.some(sonicTruth);
}
return array.some((v, i, arr) => test(v, fromInteger(i), arr));
}
some.__doc__ =
"Test whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness.";
some.__node__ = builtinNode(some);

function every(
this: ExpressionVisitor,
array?: any[],
test?: (value: any, index: Interval, array: any[]) => unknown
) {
if (!array) {
array = this.currentScale;
}
this.spendGas(array.length);
if (!test) {
return array.every(sonicTruth);
}
return array.every((v, i, arr) => test(v, fromInteger(i), arr));
}
every.__doc__ =
"Tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness.";
every.__node__ = builtinNode(every);

/**
* Obtain an array of `[key, value]` pairs of the record.
* @param record SonicWeave record.
Expand Down Expand Up @@ -2282,6 +2318,8 @@ export const BUILTIN_CONTEXT: Record<string, Interval | SonicWeaveFunction> = {
distill,
arrayReduce,
arrayRepeat,
some,
every,
kCombinations,
entries,
// CSS color generation
Expand Down
27 changes: 14 additions & 13 deletions src/stdlib/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ export const PRELUDE_VOLATILES = `
// XXX: This is only here to bypass scope optimization so that Scale Workshop can hook warn().
riff reduce(scale = $$) {
"Reduce the current/given scale by its equave. Issue a warning if the scale was already reduced.";
for (const i of scale) {
if (i < 1 or i > scale[-1])
break;
} else {
if (not scale) {
return;
}
if (every(scale >= 1 vand scale <= scale[-1])) {
warn("The scale was already reduced by its equave. Did you mean 'simplify'?");
return;
}
Expand All @@ -15,24 +15,28 @@ riff reduce(scale = $$) {

export const PRELUDE_SOURCE = `
// == Root context dependents ==
// Note that this could be golfed to:
// const ablin = i => i linear absolute,
// but it would lead to weird behavior if i is a function.
riff ablin(interval) {
"Convert interval to absolute linear representation.";
return absolute(linear(interval));
return absolute(linear interval);
}
riff ablog(interval) {
"Convert interval to absolute logarithmic representation.";
return absolute(logarithmic(interval));
return absolute(logarithmic interval);
}
riff relin(interval) {
"Convert interval to relative linear representation.";
return relative(linear(interval));
return relative(linear interval);
}
riff relog(interval) {
"Convert interval to relative logarithmic representation.";
return relative(logarithmic(interval));
return relative(logarithmic interval);
}
riff NFJS(interval) {
Expand Down Expand Up @@ -77,7 +81,7 @@ riff values(record) {
riff sanitize(interval) {
"Get rid of interval formatting, color and label.";
return bleach(simplify(interval));
return bleach(simplify interval);
}
riff sqrt(x) {
Expand Down Expand Up @@ -143,10 +147,7 @@ riff denominator(x) {
}
riff sign(x) {
"Calculate the sign of x.";
if (x > 0) return 1;
if (x < 0) return -1;
if (x === 0) return 0;
return NaN;
return 1 where x > 0 else -1 where x < 0 else 0 where x === 0 else NaN;
}
riff oddLimitOf(x, equave = 2) {
"Calculate the odd limit of x. Here 'odd' means not divisible by the equave.";
Expand Down

0 comments on commit 3fc17de

Please sign in to comment.