From 9c7bc8027a41d5416d084ca2f389f10d01a04f9d Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Wed, 24 May 2017 12:03:14 -0700 Subject: [PATCH 1/3] Implement builtin iter with a sentinel value. --- batavia/builtins/iter.js | 19 ++++++++++++++++++- tests/builtins/test_iter.py | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/batavia/builtins/iter.js b/batavia/builtins/iter.js index 271d775a6..8a7af1657 100755 --- a/batavia/builtins/iter.js +++ b/batavia/builtins/iter.js @@ -1,6 +1,7 @@ var exceptions = require('../core').exceptions var callables = require('../core').callables var type_name = require('../core').type_name +var types = require('../types') function iter(args, kwargs) { if (arguments.length !== 2) { @@ -13,7 +14,23 @@ function iter(args, kwargs) { throw new exceptions.TypeError.$pyclass('iter() expected at least 1 arguments, got 0') } if (args.length === 2) { - throw new exceptions.NotImplementedError.$pyclass("Builtin Batavia function 'iter' with callable/sentinel not implemented") + var callable = args[0] + var retval = new types.List() + var next_item + while (true) { + try { + next_item = callable.__call__([]) + } catch (e) { + if (e instanceof exceptions.StopIteration.$pyclass) { + return retval + } + throw e + } + if (next_item.__eq__(args[1])) { + return retval + } + retval.append(next_item) + } } if (args.length > 2) { throw new exceptions.TypeError.$pyclass('iter() expected at most 2 arguments, got 3') diff --git a/tests/builtins/test_iter.py b/tests/builtins/test_iter.py index 5f712d9e7..130933542 100644 --- a/tests/builtins/test_iter.py +++ b/tests/builtins/test_iter.py @@ -8,6 +8,26 @@ def test_iter_bytes(self): print(list(iter(b"abcdefgh"))) """) + def test_iter_sentinel_range(self): + self.assertCodeExecution(""" + seq = iter(range(10)) + callable = lambda: next(seq) + result = iter(callable, 3) + print(list(result)) + """) + + def test_iter_sentinel_gen(self): + self.assertCodeExecution(""" + def gen(): + abc = 'abcdefghij' + for letter in abc: + yield letter + + g = gen() + callable = lambda: next(g) + result = iter(callable, 'd') + print(list(result)) + """) class BuiltinIterFunctionTests(BuiltinFunctionTestCase, TranspileTestCase): From 5dcc80a7a7afb541bef0f15e0b9dc817e47fb43f Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Wed, 24 May 2017 12:25:59 -0700 Subject: [PATCH 2/3] Reimplement with an actual iterator. --- batavia/builtins/iter.js | 18 +-------------- batavia/types.js | 2 ++ batavia/types/CallableIterator.js | 37 +++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 batavia/types/CallableIterator.js diff --git a/batavia/builtins/iter.js b/batavia/builtins/iter.js index 8a7af1657..5a5167757 100755 --- a/batavia/builtins/iter.js +++ b/batavia/builtins/iter.js @@ -14,23 +14,7 @@ function iter(args, kwargs) { throw new exceptions.TypeError.$pyclass('iter() expected at least 1 arguments, got 0') } if (args.length === 2) { - var callable = args[0] - var retval = new types.List() - var next_item - while (true) { - try { - next_item = callable.__call__([]) - } catch (e) { - if (e instanceof exceptions.StopIteration.$pyclass) { - return retval - } - throw e - } - if (next_item.__eq__(args[1])) { - return retval - } - retval.append(next_item) - } + return new types.CallableIterator(args[0], args[1]) } if (args.length > 2) { throw new exceptions.TypeError.$pyclass('iter() expected at most 2 arguments, got 3') diff --git a/batavia/types.js b/batavia/types.js index 6aa40d3c1..c67da9891 100644 --- a/batavia/types.js +++ b/batavia/types.js @@ -45,6 +45,8 @@ types['Generator'] = require('./types/Generator') types['Range'] = require('./types/Range') types['Slice'] = require('./types/Slice') +types['CallableIterator'] = require('./types/CallableIterator') + /************************************************************************* * Type comparison defintions that match Python-like behavior. *************************************************************************/ diff --git a/batavia/types/CallableIterator.js b/batavia/types/CallableIterator.js new file mode 100644 index 000000000..fda59bf52 --- /dev/null +++ b/batavia/types/CallableIterator.js @@ -0,0 +1,37 @@ +var PyObject = require('../core').Object +var exceptions = require('../core').exceptions +var create_pyclass = require('../core').create_pyclass + +/************************************************** + * Callable Iterator + **************************************************/ + +function CallableIterator(callable, sentinel) { + PyObject.call(this) + this.callable = callable + this.sentinel = sentinel +} + +create_pyclass(CallableIterator, 'callable_iterator') + +CallableIterator.prototype.__next__ = function() { + var item = this.callable.__call__([]) + if (item.__eq__(this.sentinel)) { + throw new exceptions.StopIteration.$pyclass() + } + return item +} + +CallableIterator.prototype.__iter__ = function() { + return this +} + +CallableIterator.prototype.__str__ = function() { + return '' +} + +/************************************************** + * Module exports + **************************************************/ + +module.exports = CallableIterator From a71c350cf81861d6fb6431375d22d1742c73346b Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Wed, 24 May 2017 13:18:12 -0700 Subject: [PATCH 3/3] Make sure iterable is exhausted when encountering the sentinel value. --- batavia/types/CallableIterator.js | 6 ++++++ tests/builtins/test_iter.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/batavia/types/CallableIterator.js b/batavia/types/CallableIterator.js index fda59bf52..2e67b9121 100644 --- a/batavia/types/CallableIterator.js +++ b/batavia/types/CallableIterator.js @@ -10,13 +10,19 @@ function CallableIterator(callable, sentinel) { PyObject.call(this) this.callable = callable this.sentinel = sentinel + this.exhausted = false } create_pyclass(CallableIterator, 'callable_iterator') CallableIterator.prototype.__next__ = function() { + if (this.exhausted) { + throw new exceptions.StopIteration.$pyclass() + } + var item = this.callable.__call__([]) if (item.__eq__(this.sentinel)) { + this.exhausted = true throw new exceptions.StopIteration.$pyclass() } return item diff --git a/tests/builtins/test_iter.py b/tests/builtins/test_iter.py index 130933542..2798c82af 100644 --- a/tests/builtins/test_iter.py +++ b/tests/builtins/test_iter.py @@ -16,6 +16,24 @@ def test_iter_sentinel_range(self): print(list(result)) """) + def test_iter_sentinel_repeated(self): + self.assertCodeExecution(""" + seq = iter(range(10)) + callable = lambda: next(seq) + iterator = iter(callable, 3) + print(next(iterator)) + print(next(iterator)) + print(next(iterator)) + try: + print(next(iterator)) + except StopIteration: + pass + try: + print(next(iterator)) + except StopIteration: + pass + """) + def test_iter_sentinel_gen(self): self.assertCodeExecution(""" def gen():