Skip to content

Commit

Permalink
Differentiate between update and unary operators
Browse files Browse the repository at this point in the history
Make array elements and record values updatable.
Vectorize updates.
Simplify boolean nots at the grammar level.
  • Loading branch information
frostburn committed Apr 23, 2024
1 parent 56a7cf1 commit cd959cf
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 89 deletions.
15 changes: 11 additions & 4 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export type UnaryOperator =
| '/'
| 'lift'
| '\\'
| 'drop'
| '++'
| '--';
| 'drop';

export type UpdateOperator = '++' | '--';

export type BinaryOperator =
| 'lest'
Expand Down Expand Up @@ -247,10 +247,16 @@ export type UnaryExpression = {
type: 'UnaryExpression';
operator: UnaryOperator;
operand: Expression;
prefix: boolean;
uniform: boolean;
};

export type UpdateExpression = {
type: 'UpdateExpression';
operator: UpdateOperator;
argument: Expression;
prefix: boolean;
};

export type DownExpression = {
type: 'DownExpression';
count: number;
Expand Down Expand Up @@ -370,6 +376,7 @@ export type Expression =
| AccessExpression
| ArraySlice
| UnaryExpression
| UpdateExpression
| DownExpression
| BinaryExpression
| LabeledExpression
Expand Down
99 changes: 40 additions & 59 deletions src/grammars/sonic-weave.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@
ArraySlice: "object",
};

function UpdateExpression(operator, argument, prefix) {
return {
type: 'UpdateExpression',
operator,
argument,
prefix,
};
}

function UnaryExpression(operator, operand, uniform) {
return {
type: 'UnaryExpression',
operator,
operand,
uniform,
};
}

function BinaryExpression(operator, left, right, preferLeft, preferRight) {
return {
type: 'BinaryExpression',
Expand Down Expand Up @@ -530,19 +548,19 @@ CoalescingExpression
Conjunct = NotExpression / RelationalExpression

ConjunctionExpression
= head: Conjunct tail: (__ @$AndToken _ @Conjunct)* {
= head: NotExpression tail: (__ @$AndToken _ @NotExpression)* {
return tail.reduce(operatorReducerLite, head);
}

NotExpression
= operator: $NotToken __ operand: (RelationalExpression / NotExpression) {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: false,
};
= operators: NotToken|.., __| __ operand: RelationalExpression {
if (!operators.length) {
return operand;
}
if (operators.length & 1) {
return UnaryExpression('not', operand, false);
}
return UnaryExpression('not', UnaryExpression('not', operand, false), false);
}

RelationalOperator 'relational operator'
Expand Down Expand Up @@ -649,33 +667,15 @@ UniformUnaryOperator
= '-' / '%' / '÷'
UniformUnaryExpression
= operator: '--' operand: ExponentiationExpression {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: false,
};
= operator: '--' argument: ExponentiationExpression {
return UpdateExpression(operator, argument, true);
}
/ operator: UniformUnaryOperator '~' operand: ExponentiationExpression {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: true,
};
return UnaryExpression(operator, operand, true);
}
/ operator: UniformUnaryOperator? operand: ExponentiationExpression {
if (operator) {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: false,
};
return UnaryExpression(operator, operand, false);
}
return operand;
}
Expand All @@ -699,43 +699,24 @@ FractionExpression
ChainableUnaryOperator
= $('^' / '' / '\u2228' / '/' / LiftToken / '\\' / DropToken)
// The precedence between exponentiation and fractions is a bit uneasy.
// Uniform unary operators make a seccond appearance here to be valid right operands for exponentiation and fractions.
UnaryExpression
= operator: UniformUnaryOperator uniform: '~'? operand: LabeledExpression {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: !!uniform,
};
return UnaryExpression(operator, operand, !!uniform);
}
/ operator: ChainableUnaryOperator __ operand: (LabeledExpression / UnaryExpression) {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: false,
};
return UnaryExpression(operator, operand, false);
}
/ operator: ('--' / '++' / '+') operand: LabeledExpression {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: true,
uniform: false,
};
if (operator === '+') {
return UnaryExpression(operator, operand, false);
}
return UpdateExpression(operator, operand, true);
}
/ operand: LabeledExpression operator: ('--' / '++')? {
if (operator) {
return {
type: 'UnaryExpression',
operator,
operand,
prefix: false,
uniform: false,
}
return UpdateExpression(operator, operand, false);
}
return operand;
}
Expand Down
43 changes: 43 additions & 0 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2005,4 +2005,47 @@ describe('SonicWeave expression evaluator', () => {
const yep = evaluate('not not hotpink');
expect(yep).toBe(true);
});

it('increments a value', () => {
const {fraction} = parseSingle('let i = 0;++i;i');
expect(fraction).toBe('1');
});

it('increments an array element', () => {
const arr = evaluate('const arr = [1, 2];arr[1]++;map(str, arr)');
expect(arr).toEqual(['1', '3']);
});

it('increments a record value', () => {
const rec = evaluate('const rec = {a: 1, b: 2};rec["a"]++;rec') as Record<
string,
Interval
>;
expect(rec.a.toInteger()).toBe(2);
expect(rec.b.toInteger()).toBe(2);
});

it('increments an array (prefix)', () => {
const arr = evaluate('let arr = [1, 2];++arr') as Interval[];
expect(arr[0].toInteger()).toBe(2);
expect(arr[1].toInteger()).toBe(3);
});

it('increments (?) an array (postfix)', () => {
const arr = evaluate('let arr = [1, 2];arr++') as Interval[];
expect(arr[0].toInteger()).toBe(1);
expect(arr[1].toInteger()).toBe(2);
});

it('increments an array (postfix)', () => {
const arr = evaluate('let arr = [1, 2];arr++;arr') as Interval[];
expect(arr[0].toInteger()).toBe(2);
expect(arr[1].toInteger()).toBe(3);
});

it('reject literal increment', () => {
expect(() => evaluate('1++')).toThrow(
'Only identifiers, array elements or record values may be incremented or decremented.'
);
});
});
7 changes: 2 additions & 5 deletions src/parser/__tests__/sonic-weave-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
callee: {type: 'Identifier', id: 'foo'},
args: [],
},
prefix: true,
uniform: false,
},
});
Expand All @@ -585,11 +584,10 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
expect(ast).toEqual({
type: 'ExpressionStatement',
expression: {
type: 'UnaryExpression',
type: 'UpdateExpression',
operator: '--',
operand: {type: 'Identifier', id: 'i'},
argument: {type: 'Identifier', id: 'i'},
prefix: false,
uniform: false,
},
});
});
Expand Down Expand Up @@ -653,7 +651,6 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
type: 'UnaryExpression',
operator: '-',
operand: {type: 'IntegerLiteral', value: 2n},
prefix: true,
uniform: false,
},
],
Expand Down
83 changes: 62 additions & 21 deletions src/parser/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ import {
UnaryExpression,
IterationKind,
TemplateArgument,
UpdateExpression,
UpdateOperator,
} from '../ast';
import {type StatementVisitor} from './statement';

Expand Down Expand Up @@ -292,6 +294,8 @@ export class ExpressionVisitor {
return this.visitArraySlice(node);
case 'UnaryExpression':
return this.visitUnaryExpression(node);
case 'UpdateExpression':
return this.visitUpdateExpression(node);
case 'BinaryExpression':
return this.visitBinaryExpression(node);
case 'LabeledExpression':
Expand Down Expand Up @@ -944,27 +948,8 @@ export class ExpressionVisitor {
case 'drop':
return operand.drop(this.rootContext);
}
if (operator === 'not') {
// The runtime shouldn't let you get here.
throw new Error('Unexpected unary operation.');
}
operator satisfies '++' | '--';

let newValue: Interval;
if (operator === '++') {
newValue = operand.add(linearOne());
} else {
newValue = operand.sub(linearOne());
}
if (node.operand.type !== 'Identifier') {
throw new Error('Cannot increment/decrement a value.');
}
const name = node.operand.id;
this.set(name, newValue);
if (node.prefix) {
return newValue;
}
return operand;
// The runtime shouldn't let you get here.
throw new Error('Unexpected unary operation.');
}

protected visitUnaryExpression(node: UnaryExpression) {
Expand All @@ -975,6 +960,62 @@ export class ExpressionVisitor {
return this.unaryOperate(operand, node);
}

protected updateArgument(
argument: SonicWeaveValue,
operator: UpdateOperator
): Interval | Interval[] {
if (Array.isArray(argument)) {
const u = this.updateArgument.bind(this);
return argument.map(x => u(x, operator)) as Interval[];
}
if (typeof argument === 'boolean' || argument instanceof Interval) {
if (operator === '++') {
return upcastBool(argument).add(linearOne());
}
return upcastBool(argument).sub(linearOne());
}
throw new Error('Only intervals may be incremented or decremented.');
}

protected visitUpdateExpression(node: UpdateExpression) {
const argument = this.visit(node.argument);
const newValue = this.updateArgument(argument, node.operator);
if (node.argument.type === 'Identifier') {
this.set(node.argument.id, newValue);
} else if (node.argument.type === 'AccessExpression') {
const key = this.visit(node.argument.key);
const object = arrayRecordOrString(
this.visit(node.argument.object),
'Only array elements or record values may be incremented or decremented.'
);
if (typeof object === 'string') {
throw new Error('Strings are immutable.');
}
if (Array.isArray(object)) {
if (typeof key === 'boolean' || key instanceof Interval) {
const index = upcastBool(key).toInteger();
object[index] = newValue as SonicWeavePrimitive;
} else {
throw new Error('Array indices must be intervals.');
}
} else {
if (typeof key === 'string') {
object[key] = newValue as SonicWeavePrimitive;
} else {
throw new Error('Record keys must be strings.');
}
}
} else {
throw new Error(
'Only identifiers, array elements or record values may be incremented or decremented.'
);
}
if (node.prefix) {
return newValue;
}
return argument;
}

protected tensor(
left: SonicWeaveValue,
right: SonicWeaveValue,
Expand Down

0 comments on commit cd959cf

Please sign in to comment.