diff --git a/src/stdlib/__tests__/__snapshots__/pylib.ts.snap b/src/stdlib/__tests__/__snapshots__/pylib.ts.snap index d945c0379..ed7d150a6 100644 --- a/src/stdlib/__tests__/__snapshots__/pylib.ts.snap +++ b/src/stdlib/__tests__/__snapshots__/pylib.ts.snap @@ -97,7 +97,7 @@ Object { "code": "True * 2", "displayResult": Array [], "numErrors": 1, - "parsedErrors": "Line 1: Error: Expected number on left hand side of operation, got boolean.", + "parsedErrors": "Line 1: Error: Invalid types for multiply operation: boolean, bigint", "result": undefined, "resultStatus": "error", "visualiseListResult": Array [], diff --git a/src/stdlib/__tests__/pylib.ts b/src/stdlib/__tests__/pylib.ts index 6c24f7c6d..43d2e1299 100644 --- a/src/stdlib/__tests__/pylib.ts +++ b/src/stdlib/__tests__/pylib.ts @@ -98,9 +98,7 @@ test('cannot multiply non-number values', () => { True * 2 `, { chapter: Chapter.PYTHON_1, native: true } - ).toMatchInlineSnapshot( - `"Line 1: Error: Expected number on left hand side of operation, got boolean."` - ) + ).toMatchInlineSnapshot(`"Line 1: Error: Invalid types for multiply operation: boolean, bigint"`) }) test('dividing integer and float is ok', () => { diff --git a/src/stdlib/pylib.ts b/src/stdlib/pylib.ts index 5a8ef11c7..a06814955 100644 --- a/src/stdlib/pylib.ts +++ b/src/stdlib/pylib.ts @@ -8,6 +8,15 @@ export function is_int(v: Value) { return typeof v === 'bigint' } +function __is_numeric(v: Value) { + return is_int(v) || is_float(v) +} + +function __is_string(v: Value) { + // Retrieved from https://stackoverflow.com/questions/4059147/check-if-a-variable-is-a-string-in-javascript + return typeof v === 'string' || v instanceof String +} + export function __py_adder(x: Value, y: Value) { if (typeof x === typeof y) { return x + y @@ -18,14 +27,17 @@ export function __py_adder(x: Value, y: Value) { if (typeof y === 'bigint') { return x + Number(y) } - return x + y + if (__is_numeric(x) && __is_numeric(y)) { + return x + y + } + throw new Error(`Invalid types for addition operation: ${typeof x}, ${typeof y}`) } export function __py_minuser(x: Value, y: Value) { - if (!(typeof x === 'bigint') && !(typeof x === 'number')) { + if (!__is_numeric(x)) { throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.') } - if (!(typeof y === 'bigint') && !(typeof y === 'number')) { + if (!__is_numeric(y)) { throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.') } if (typeof x === 'bigint' && typeof y === 'bigint') { @@ -40,16 +52,20 @@ export function __py_minuser(x: Value, y: Value) { if (typeof x === 'number' && typeof y === 'bigint') { return x - Number(y) } - return NaN + throw new Error(`Invalid types for subtraction operation: ${typeof x}, ${typeof y}`) } export function __py_multiplier(x: Value, y: Value) { - if (!(typeof x === 'bigint') && !(typeof x === 'number')) { - throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.') - } - if (!(typeof y === 'bigint') && !(typeof y === 'number')) { - throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.') + if ( + !( + (__is_numeric(x) && __is_numeric(y)) || + (__is_string(x) && __is_numeric(y)) || + (__is_string(y) && __is_numeric(x)) + ) + ) { + throw new Error(`Invalid types for multiply operation: ${typeof x}, ${typeof y}`) } + if (typeof x === 'bigint' && typeof y === 'bigint') { return x * y } @@ -62,14 +78,20 @@ export function __py_multiplier(x: Value, y: Value) { if (typeof x === 'number' && typeof y === 'bigint') { return x * Number(y) } - return NaN + if (typeof x == 'number' && __is_string(y)) { + return y.repeat(x) + } + if (typeof y == 'number' && __is_string(x)) { + return x.repeat(y) + } + throw new Error(`Invalid types for multiply operation: ${typeof x}, ${typeof y}`) } export function __py_divider(x: Value, y: Value) { - if (!(typeof x === 'bigint') && !(typeof x === 'number')) { + if (!__is_numeric(x)) { throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.') } - if (!(typeof y === 'bigint') && !(typeof y === 'number')) { + if (!__is_numeric(y)) { throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.') } if (typeof x === 'bigint' && typeof y === 'bigint') { @@ -84,14 +106,14 @@ export function __py_divider(x: Value, y: Value) { if (typeof x === 'number' && typeof y === 'bigint') { return x / Number(y) } - return NaN + throw new Error(`Invalid types for divide operation: ${typeof x}, ${typeof y}`) } export function __py_modder(x: Value, y: Value) { - if (!(typeof x === 'bigint') && !(typeof x === 'number')) { + if (!__is_numeric(x)) { throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.') } - if (!(typeof y === 'bigint') && !(typeof y === 'number')) { + if (!__is_numeric(y)) { throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.') } if (typeof x === 'bigint' && typeof y === 'bigint') { @@ -106,14 +128,14 @@ export function __py_modder(x: Value, y: Value) { if (typeof x === 'number' && typeof y === 'bigint') { return x % Number(y) } - return NaN + throw new Error(`Invalid types for modulo operation: ${typeof x}, ${typeof y}`) } export function __py_powerer(x: Value, y: Value) { - if (!(typeof x === 'bigint') && !(typeof x === 'number')) { + if (!__is_numeric(x)) { throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.') } - if (!(typeof y === 'bigint') && !(typeof y === 'number')) { + if (!__is_numeric(y)) { throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.') } if (typeof x === 'bigint' && typeof y === 'bigint') { @@ -123,13 +145,16 @@ export function __py_powerer(x: Value, y: Value) { } return res } + if (typeof x === 'number' && typeof y === 'number') { + return Math.pow(x, y) + } if (typeof x === 'bigint' && typeof y === 'number') { return Math.pow(Number(x), y) } if (typeof x === 'number' && typeof y === 'bigint') { return Math.pow(x, Number(y)) } - return Math.pow(Number(x), Number(y)) + throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`) } export function __py_floorer(x: Value, y: Value) { @@ -137,122 +162,202 @@ export function __py_floorer(x: Value, y: Value) { } export function __py_unary_plus(x: Value) { - if (typeof x === 'bigint') { + if (__is_numeric(x)) { return +Number(x) } - return +x + throw new Error(`Invalid type for unary plus operation: ${typeof x}`) } export function math_abs(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.abs(Number(x)) } export function math_acos(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.acos(Number(x)) } export function math_acosh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.acosh(Number(x)) } export function math_asin(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.asin(Number(x)) } export function math_asinh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.asinh(Number(x)) } export function math_atan(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.atan(Number(x)) } export function math_atan2(y: Value, x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.atan2(Number(y), Number(x)) } export function math_atanh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.atanh(Number(x)) } export function math_cbrt(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.cbrt(Number(x)) } export function math_ceil(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.ceil(Number(x)) } export function math_clz32(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.clz32(Number(x)) } export function math_cos(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.cos(Number(x)) } export function math_cosh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.cosh(Number(x)) } export function math_exp(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.exp(Number(x)) } export function math_expm1(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.expm1(Number(x)) } export function math_floor(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.floor(Number(x)) } export function math_fround(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.fround(Number(x)) } export function math_hypot(...elements: Value[]) { const coercedElements: number[] = elements.map(el => { + if (!__is_numeric(el)) { + throw new Error(`Invalid type for operation: ${typeof el}`) + } return Number(el) }) return Math.hypot(...coercedElements) } export function math_imul(x: Value, y: Value) { + if (!__is_numeric(x) || !__is_numeric(y)) { + throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`) + } return Math.imul(Number(x), Number(y)) } export function math_log(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.log(Number(x)) } export function math_log1p(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.log1p(Number(x)) } export function math_log2(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.log2(Number(x)) } export function math_log10(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.log10(Number(x)) } export function math_max(...elements: Value[]) { + // TODO: Python max also supports strings! const coercedElements: number[] = elements.map(el => { + if (!__is_numeric(el)) { + throw new Error(`Invalid type for operation: ${typeof el}`) + } return Number(el) }) return Math.max(...coercedElements) } export function math_min(...elements: Value[]) { + // TODO: Python min also supports strings! const coercedElements: number[] = elements.map(el => { + if (!__is_numeric(el)) { + throw new Error(`Invalid type for operation: ${typeof el}`) + } return Number(el) }) return Math.min(...coercedElements) } export function math_pow(x: Value, y: Value) { + if (!__is_numeric(x) || !__is_numeric(y)) { + throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`) + } return Math.pow(Number(x), Number(y)) } @@ -261,33 +366,57 @@ export function math_random() { } export function math_round(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.round(Number(x)) } export function math_sign(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.sign(Number(x)) } export function math_sin(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.sin(Number(x)) } export function math_sinh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.sinh(Number(x)) } export function math_sqrt(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.sqrt(Number(x)) } export function math_tan(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.tan(Number(x)) } export function math_tanh(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.tanh(Number(x)) } export function math_trunc(x: Value) { + if (!__is_numeric(x)) { + throw new Error(`Invalid type for operation: ${typeof x}`) + } return Math.trunc(Number(x)) }