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

Feature: arithmetic operators #11

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 1 addition & 2 deletions lib/expressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ class OperationExpression {
const args = this.operands.map((x) => toValues(x));
const operator = OPERATORS[this.operator];
if (!operator) throw new Error(`Unknown operator: ${operator}`);
if (this.operator.length > 1) return operator(...args);
return args.reduce(operator);
return operator(...args);
}
}

Expand Down
6 changes: 2 additions & 4 deletions lib/operators.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
'use strict';

const { listp, head, tail } = require('./utils.js');
const arithmetic = require('./operators/arithmetic.js');

module.exports = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
eq: (a, b) => a === b,
list: (...list) => (listp(list) ? list : null),
car: (list) => (listp(list) ? head(list) : null),
cdr: (list) => (listp(list) ? tail(list) : null),
...arithmetic,
};
61 changes: 61 additions & 0 deletions lib/operators/arithmetic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const euclideanAlgorythm = (a, b) => {
const r = a % b;
if (r === 0) return b;
return euclideanAlgorythm(b, r);
};

const gcd = (...list) => {
if (list.length === 0) return 0;
if (list.length === 1) return Math.abs(list[0]);
return list.reduce(euclideanAlgorythm);
};

const lcm = (...list) => {
if (list.length === 0) return 1;
if (list.length === 1) return Math.abs(list[0]);
return list.reduce((a, b) => Math.abs(a * b) / euclideanAlgorythm(a, b));
};

const addition = (...list) => {
if (list.length === 0) return 0;
return list.reduce((a, b) => a + b);
};

const subtraction = (...list) => {
if (list.length === 0) return 0;
if (list.length === 1) return -list[0];
return list.reduce((a, b) => a - b);
};

const multiplication = (...list) => {
if (list.length === 0) return 1;
return list.reduce((a, b) => a * b);
};

const division = (...list) => {
if (list.length === 0) throw new Error('Division by zero');
const isZero = (element) => element === 0;
const hasZero = list.some(isZero);
if (hasZero) throw new Error('Division by zero');
if (list.length === 1) return list[0];
if (list.length === 2) return list[0] / list[1];
const [first, ...rest] = list;
return first / multiplication(...rest);
};

module.exports = {
'+': addition,
'-': subtraction,
'*': multiplication,
'/': division,
mod: (a, b) => ((a % b) + b) % b,
rem: (a, b) => a % b,
incf: (a, b = 1) => a + b,
decf: (a, b = 1) => a - b,
'1+': (a) => a + 1,
'1-': (a) => a - 1,
gcd,
lcm,
};
271 changes: 271 additions & 0 deletions test/arithmetic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
'use strict';
const assert = require('node:assert');
const { describe, it } = require('node:test');
const { evaluate } = require('..');

describe('Arithmetic operators', () => {
describe('Addition', () => {
it('Evaluate addition', () => {
const program = '(+ x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 + 2;
assert.strictEqual(result, expected, 'Addition operator failed');
});

it('Evaluate addition on more than two arguments', () => {
const program = '(+ x y z)';
const context = { x: 1, y: 2, z: 3 };
const result = evaluate(program, context);
const expected = 1 + 2 + 3;
assert.strictEqual(result, expected, 'Addition operator failed');
});
});

describe('Subtraction', () => {
it('Evaluate subtraction', () => {
const program = '(- x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 - 2;
assert.strictEqual(result, expected, 'Subtraction operator failed');
});

it('Evaluate subtraction on more than two arguments', () => {
const program = '(- x y z)';
const context = { x: 1, y: 2, z: 3 };
const result = evaluate(program, context);
const expected = 1 - 2 - 3;
assert.strictEqual(result, expected, 'Subtraction operator failed');
});
});

describe('Multiplication', () => {
it('Evaluate multiplication', () => {
const program = '(* x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 * 2;
assert.strictEqual(result, expected, 'Multiplication operator failed');
});

it('Evaluate multiplication with no arguments', () => {
const program = '(* )';
const result = evaluate(program);
const expected = 1;
assert.strictEqual(result, expected, 'Multiplication operator failed');
});

it('Evaluate multiplication on more than two arguments', () => {
const program = '(* x y z)';
const context = { x: 1, y: 2, z: 3 };
const result = evaluate(program, context);
const expected = 1 * 2 * 3;
assert.strictEqual(result, expected, 'Multiplication operator failed');
});
});

describe('Division', () => {
it('Evaluate division', () => {
const program = '(/ x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 / 2;
assert.strictEqual(result, expected, 'Division operator failed');
});

it('Evaluate division on more than two arguments', () => {
const program = '(/ x y z)';
const context = { x: 3, y: 4, z: 5 };
const result = evaluate(program, context);
const expected = 3 / (4 * 5);
assert.strictEqual(result, expected, 'Division operator failed');
});
});

it('Evaluate modulus', () => {
const program = '(mod x y)';
const context = { x: 20, y: 10 };
const result = evaluate(program, context);
const expected = 20 % 10;
assert.strictEqual(result, expected, 'Modulus operator failed');
});

it('Evaluate modulus with negative numbers', () => {
const program = '(mod x y)';
const context = { x: -1, y: 5 };
const result = evaluate(program, context);
const expected = ((-1 % 5) + 5) % 5;
assert.strictEqual(
result,
expected,
'Modulus operator with negative numbers failed',
);
});

it('Evaluate remainder', () => {
const program = '(rem x y)';
const context = { x: -1, y: 5 };
const result = evaluate(program, context);
const expected = -1 % 5;
assert.strictEqual(result, expected, 'Reminder operator failed');
});

it('Evaluate increment', () => {
const program = '(incf x)';
const context = { x: 1 };
const result = evaluate(program, context);
const expected = 1 + 1;
assert.strictEqual(result, expected, 'Increment operator failed');
});

it('Evaluate increment with second argument', () => {
const program = '(incf x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 + 2;
assert.strictEqual(
result,
expected,
'Increment operator with second argument failed',
);
});

it('Evaluate decrement', () => {
const program = '(decf x)';
const context = { x: 1 };
const result = evaluate(program, context);
const expected = 1 - 1;
assert.strictEqual(result, expected, 'Decrement operator failed');
});

it('Evaluate decrement with second argument', () => {
const program = '(decf x y)';
const context = { x: 1, y: 2 };
const result = evaluate(program, context);
const expected = 1 - 2;
assert.strictEqual(
result,
expected,
'Decrement operator with second argument failed',
);
});

it('Evaluate increment shortcut', () => {
const program = '(1+ x)';
const context = { x: 1 };
const result = evaluate(program, context);
const expected = 1 + 1;
assert.strictEqual(result, expected, 'Increment shortcut operator fail');
});

it('Evaluate decrement shortcut', () => {
const program = '(1- x)';
const context = { x: 1 };
const result = evaluate(program, context);
const expected = 1 - 1;
assert.strictEqual(result, expected, 'Decrement shortcut operator fail');
});

it('Evaluate complex arithmetic expression', () => {
const program = '(+ (mod x y) (incf y))';
const context = { x: -1, y: 5 };
const result = evaluate(program, context);
const expected = (((-1 % 5) + 5) % 5) + 6;
assert.strictEqual(
result,
expected,
'Complex arithmetic expression failed',
);
});

describe('Greatest common divisor', () => {
it('Evaluate gcd on two arguments', () => {
const program = '(gcd x y)';
const context = { x: 20, y: 10 };
const result = evaluate(program, context);
const expected = 10;
assert.strictEqual(result, expected, 'GCD operator failed');
});

it('Evaluate gcd on multiple arguments', () => {
const program = '(gcd x y z)';
const context = { x: 20, y: 10, z: 5 };
const result = evaluate(program, context);
const expected = 5;
assert.strictEqual(result, expected, 'GCD operator failed');
});

it('Evaluate gcd on some negative arguments', () => {
const program = '(gcd x y)';
const context = { x: -20, y: 10 };
const result = evaluate(program, context);
const expected = 10;
assert.strictEqual(result, expected, 'GCD operator failed');
});

it('Evaluate gcd on single negative argument', () => {
const program = '(gcd x)';
const context = { x: -20 };
const result = evaluate(program, context);
const expected = 20;
assert.strictEqual(result, expected, 'GCD operator failed');
});

it('Evaluate gcd on no arguments', () => {
const program = '(gcd)';
const result = evaluate(program);
const expected = 0;
assert.strictEqual(result, expected, 'GCD operator failed');
});

it('Evaluate gcd on single argument', () => {
const program = '(gcd x)';
const context = { x: 20 };
const result = evaluate(program, context);
const expected = 20;
assert.strictEqual(result, expected, 'GCD operator failed');
});
});

describe('Least common multiple', () => {
it('Evaluate lcm on two arguments', () => {
const program = '(lcm x y)';
const context = { x: 20, y: 10 };
const result = evaluate(program, context);
const expected = 20;
assert.strictEqual(result, expected, 'LCM operator failed');
});

it('Evaluate lcm on multiple arguments', () => {
const program = '(lcm x y z)';
const context = { x: 1, y: 2, z: 3 };
const result = evaluate(program, context);
const expected = 6;
assert.strictEqual(result, expected, 'LCM operator failed');
});

it('Evaluate lcm on some negative arguments', () => {
const program = '(lcm x y)';
const context = { x: -20, y: 10 };
const result = evaluate(program, context);
const expected = 20;
assert.strictEqual(result, expected, 'LCM operator failed');
});

it('Evaluate lcm on single negative argument', () => {
const program = '(lcm x)';
const context = { x: -20 };
const result = evaluate(program, context);
const expected = 20;
assert.strictEqual(result, expected, 'LCM operator failed');
});

it('Evaluate lcm on no arguments', () => {
const program = '(lcm)';
const result = evaluate(program);
const expected = 1;
assert.strictEqual(result, expected, 'LCM operator failed');
});
});
});