From c0a8ce9e8cd036f43a13e2782ff57aaeaa47594c Mon Sep 17 00:00:00 2001 From: Nicholas Lambourne Date: Tue, 15 May 2018 16:26:10 -0400 Subject: [PATCH 1/3] fixed implementation of hex() builtin - all tests pass --- batavia/builtins/hex.js | 27 ++++++++++++++++++++++++--- tests/builtins/test_hex.py | 20 -------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/batavia/builtins/hex.js b/batavia/builtins/hex.js index d29db471d..e8bb6da51 100755 --- a/batavia/builtins/hex.js +++ b/batavia/builtins/hex.js @@ -1,11 +1,32 @@ +var BigNumber = require('bignumber.js') var exceptions = require('../core').exceptions +var type_name = require('../core').type_name +var types = require('../types') function hex(args, kwargs) { if (args.length !== 1) { throw new exceptions.TypeError.$pyclass('hex() takes exactly one argument (' + args.length + ' given)') - }; - var int = args[0].val - return '0x' + int.toString(16) + } + let value = args[0] + let supported_types = [types.Bool, types.Int] + let unsupported_types = [types.Bytearray, types.Bytes, types.Complex, types.Dict, types.Float, types.FrozenSet, + types.List, types.NoneType, types.NotImplementedType, types.Range, types.Set, types.Slice, types.Str, + types.Tuple] + if (types.isinstance(value, supported_types)) { + value = value.__int__() + // Check for unsupported types and classes (type_name = 'type' when arg is a class) + } else if (types.isinstance(value, unsupported_types) || type_name(value) === 'type') { + throw new exceptions.TypeError.$pyclass("'" + type_name(value) + "' object cannot be interpreted as an integer") + } + // Javascript does not have native support for intergers as large as those supported by Python (uses Infinity) + value = new BigNumber(value) + // Javascript represents negative hex differently (e.g. -5 == (JS: 0x-5 || Py: -0x5)) + if (value < 0) { + value = value.negated() + return '-0x' + value.toString(16) + } else { + return '0x' + value.toString(16) + } } hex.__doc__ = "hex(number) -> string\n\nReturn the hexadecimal representation of an integer.\n\n >>> hex(3735928559)\n '0xdeadbeef'\n" diff --git a/tests/builtins/test_hex.py b/tests/builtins/test_hex.py index 1f5d95eb4..5596f4fed 100644 --- a/tests/builtins/test_hex.py +++ b/tests/builtins/test_hex.py @@ -12,23 +12,3 @@ def test_hex(self): class BuiltinHexFunctionTests(BuiltinFunctionTestCase, TranspileTestCase): function = "hex" - - not_implemented = [ - 'test_bool', - 'test_bytearray', - 'test_bytes', - 'test_class', - 'test_complex', - 'test_dict', - 'test_float', - 'test_frozenset', - 'test_int', - 'test_list', - 'test_None', - 'test_NotImplemented', - 'test_range', - 'test_set', - 'test_slice', - 'test_str', - 'test_tuple', - ] From bc4e28bb077e9789a61a59d59e98ac0e9e651c4b Mon Sep 17 00:00:00 2001 From: Nicholas Lambourne Date: Wed, 16 May 2018 16:54:52 -0400 Subject: [PATCH 2/3] got sum working for other datatypes --- batavia/builtins/sum.js | 56 ++++++++++++++++++++++++++++++-------- tests/builtins/test_sum.py | 11 ++------ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/batavia/builtins/sum.js b/batavia/builtins/sum.js index ee146fc88..ee6db7a3f 100755 --- a/batavia/builtins/sum.js +++ b/batavia/builtins/sum.js @@ -10,26 +10,60 @@ function sum(args, kwargs) { throw new exceptions.TypeError.$pyclass("sum() doesn't accept keyword arguments") } if (!args || args.length === 0) { - throw new exceptions.TypeError.$pyclass('sum() expected at least 1 argument, got ' + args.length) + throw new exceptions.TypeError.$pyclass('sum expected at least 1 arguments, got ' + args.length) } if (args.length > 2) { - throw new exceptions.TypeError.$pyclass('sum() expected at most 2 argument, got ' + args.length) + throw new exceptions.TypeError.$pyclass('sum expected at most 2 arguments, got ' + args.length) } if (!args[0].__iter__) { throw new exceptions.TypeError.$pyclass("'" + type_name(args[0]) + "' object is not iterable") } - - try { - return args[0].reduce(function(a, b) { + let value = args[0] + // This is unique behaviour that is handled before types are resolved and added. + // Empty strings, sets resolve to zero when provided as a single argument to sum() + let resolve_to_zero_types = [types.Set, types.Str] + if (types.isinstance(value, resolve_to_zero_types)) { + if (value === '' || (types.isinstance(value, types.Set) && value.toString() === (new types.Set()).toString())) { + return new types.Int(0) + } else if (types.isinstance(value, types.Str)) { + // Reject all non-empty strings. + return new types.Int(0).__add__(value) // This will throw the correct TypeError + } + } + // Sets need to be handled differently to other iterable types due to use of dict for data. + if (types.isinstance(value, [types.Set, types.FrozenSet])) { + return value.data.keys().reduce(function(a, b) { + return a.__add__(b) + }, new types.Int(0)) + } else if (types.isinstance(value, types.Range)) { + // This doesn't work yet + let retval = types.Int(0) + for (let i = value.start; i < value.end; i += value.step) { + retval = retval.__add__(new types.Int(value.val[i])) + } + return retval + } else if (types.isinstance(value, types.Dict)) { + // Sum of dict is sum of keys + return value.keys().reduce(function(a, b) { return a.__add__(b) }, new types.Int(0)) - } catch (err) { - // a and b could fail to add due to many possible type incompatibilities, - // all of which would need to be reflected in this error message - - // but we don't have to check for them here, because we've already - // tested for them in __add__. - throw new exceptions.TypeError.$pyclass(err.msg) + } else if (types.isinstance(value, [types.Bytes, types.Bytearray])) { + // Sum of bytearray is sum of internal value + if (types.isinstance(value, types.Bytearray)) { + value = value.valueOf() + } + let retval = new types.Int(0) + // Sum of non-empty byte-strings is the sum of ASCII values of each byte + if (value.__len__() > 0) { + for (let i = 0; i < value.__len__(); i++) { + retval = retval.__add__(new types.Int(value.val[i])) + } + } + return retval } + return value.reduce(function(a, b) { + return a.__add__(b) + }, new types.Int(0)) } sum.__doc__ = "sum(iterable[, start]) -> value\n\nReturn the sum of an iterable of numbers (NOT strings) plus the value\nof parameter 'start' (which defaults to 0). When the iterable is\nempty, return start." diff --git a/tests/builtins/test_sum.py b/tests/builtins/test_sum.py index eb4e04003..d10f9f9b3 100644 --- a/tests/builtins/test_sum.py +++ b/tests/builtins/test_sum.py @@ -37,12 +37,7 @@ class BuiltinSumFunctionTests(BuiltinFunctionTestCase, TranspileTestCase): function = "sum" not_implemented = [ - 'test_noargs', - 'test_bytearray', - 'test_bytes', - 'test_dict', - 'test_frozenset', - 'test_range', - 'test_set', - 'test_str', + 'test_frozenset' # This works, but python dict.keys() returns non-deterministically + 'test_range', # This has been implemented, but fails upstream on isinstance. + 'test_set' # This works, but python dict.keys() returns non-deterministically ] From 615b4d4ee5ad3e35464698641a846fa352a30ddd Mon Sep 17 00:00:00 2001 From: Nicholas Lambourne Date: Thu, 17 May 2018 12:32:10 -0400 Subject: [PATCH 3/3] fixed categorisation of flakey tests --- batavia/builtins/sum.js | 13 +++++++------ tests/builtins/test_sum.py | 12 ++++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/batavia/builtins/sum.js b/batavia/builtins/sum.js index ee6db7a3f..14adde425 100755 --- a/batavia/builtins/sum.js +++ b/batavia/builtins/sum.js @@ -20,14 +20,14 @@ function sum(args, kwargs) { } let value = args[0] // This is unique behaviour that is handled before types are resolved and added. - // Empty strings, sets resolve to zero when provided as a single argument to sum() + // Empty strings, sets resolve to zero when provided as a single argument to sum(). let resolve_to_zero_types = [types.Set, types.Str] if (types.isinstance(value, resolve_to_zero_types)) { if (value === '' || (types.isinstance(value, types.Set) && value.toString() === (new types.Set()).toString())) { return new types.Int(0) } else if (types.isinstance(value, types.Str)) { // Reject all non-empty strings. - return new types.Int(0).__add__(value) // This will throw the correct TypeError + return new types.Int(0).__add__(value) // This will throw the correct TypeError. } } // Sets need to be handled differently to other iterable types due to use of dict for data. @@ -36,24 +36,24 @@ function sum(args, kwargs) { return a.__add__(b) }, new types.Int(0)) } else if (types.isinstance(value, types.Range)) { - // This doesn't work yet + // This doesn't work yet due to upstream error. let retval = types.Int(0) for (let i = value.start; i < value.end; i += value.step) { retval = retval.__add__(new types.Int(value.val[i])) } return retval } else if (types.isinstance(value, types.Dict)) { - // Sum of dict is sum of keys + // Sum of dict is sum of keys. return value.keys().reduce(function(a, b) { return a.__add__(b) }, new types.Int(0)) } else if (types.isinstance(value, [types.Bytes, types.Bytearray])) { - // Sum of bytearray is sum of internal value + // Sum of bytearray is sum of internal value. if (types.isinstance(value, types.Bytearray)) { value = value.valueOf() } let retval = new types.Int(0) - // Sum of non-empty byte-strings is the sum of ASCII values of each byte + // Sum of non-empty byte-strings is the sum of ASCII values of each byte. if (value.__len__() > 0) { for (let i = 0; i < value.__len__(); i++) { retval = retval.__add__(new types.Int(value.val[i])) @@ -65,6 +65,7 @@ function sum(args, kwargs) { return a.__add__(b) }, new types.Int(0)) } + sum.__doc__ = "sum(iterable[, start]) -> value\n\nReturn the sum of an iterable of numbers (NOT strings) plus the value\nof parameter 'start' (which defaults to 0). When the iterable is\nempty, return start." module.exports = sum diff --git a/tests/builtins/test_sum.py b/tests/builtins/test_sum.py index d10f9f9b3..d58439eb9 100644 --- a/tests/builtins/test_sum.py +++ b/tests/builtins/test_sum.py @@ -36,8 +36,16 @@ def test_sum_mix_floats_and_ints(self): class BuiltinSumFunctionTests(BuiltinFunctionTestCase, TranspileTestCase): function = "sum" + # these are implemented, but the exact exception thrown depends on the order + # that they are iterated on being the exact same in both CPython and batavia, + # which is not guaranteed. (if an unsupported string follows an int, the error + # will be different than if it followed a float) + + is_flakey = [ + 'test_frozenset', # This works, but python dict.keys() returns non-deterministically + 'test_set', # This works, but python dict.keys() returns non-deterministically. + ] + not_implemented = [ - 'test_frozenset' # This works, but python dict.keys() returns non-deterministically 'test_range', # This has been implemented, but fails upstream on isinstance. - 'test_set' # This works, but python dict.keys() returns non-deterministically ]