Skip to content

Commit 2ae42ae

Browse files
committed
Make reversed() return lazy reverse cursors with __reversed__ methods on builtin containers, live length hints, and CPython iterator type names
1 parent e4a0bc4 commit 2ae42ae

9 files changed

Lines changed: 977 additions & 139 deletions

File tree

‎src/DotPython.Runtime.Managed/Execution/ManagedObjectProtocols.cs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,11 @@ when name is not ("__cause__" or "__context__" or "__suppress_context__")
430430
return (PythonValue?)causeSource.Cause ?? PythonNoneValue.Instance;
431431
case PythonExceptionValue suppressSource when name == "__suppress_context__":
432432
return PythonTruthValue.FromBoolean(suppressSource.SuppressContext);
433+
case PythonListValue
434+
or PythonRangeValue
435+
or PythonDictionaryValue
436+
or PythonDictionaryViewValue when name == "__reversed__":
437+
return new PythonBoundMethodValue(name, target, PythonReverseIterators.Method);
433438
case PythonIteratorValue iterator when name is "__iter__" or "__next__":
434439
return new PythonBoundMethodValue(
435440
name,
@@ -1673,6 +1678,8 @@ iterator.Iterable is not PythonSequenceIteratorSourceValue
16731678
UserObjectProtocols.Dispatcher?.CheckIterationWork(span);
16741679
switch (iterator.Iterable)
16751680
{
1681+
case PythonReverseIteratorSourceValue reverse:
1682+
return PythonReverseIterators.TryGetNext(iterator, reverse, out value, span);
16761683
case PythonFileValue file:
16771684
{
16781685
EnsureFileOpen(file, span);
@@ -1916,7 +1923,7 @@ iterator.Iterable is not PythonSequenceIteratorSourceValue
19161923
return false;
19171924
}
19181925

1919-
private static void ValidateIteratorSize(
1926+
internal static void ValidateIteratorSize(
19201927
PythonIteratorValue iterator,
19211928
int count,
19221929
string kind,

‎src/DotPython.Runtime.Managed/Execution/PythonBuiltinFunctions.cs‎

Lines changed: 2 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -361,90 +361,12 @@ Func<PythonValue, PythonPropertyValue> rebind
361361
// reversed
362362
// ----------------------------------------------------------------------------
363363

364-
private static PythonIteratorValue Reversed(IReadOnlyList<PythonValue> arguments, TextSpan span)
364+
private static PythonValue Reversed(IReadOnlyList<PythonValue> arguments, TextSpan span)
365365
{
366366
RequireArgumentCount("reversed", arguments, 1, 1, span);
367-
var sequence = arguments[0];
368-
if (
369-
sequence
370-
is PythonMappingProxyValue
371-
or PythonDictionaryValue
372-
or PythonDictionaryViewValue
373-
)
374-
{
375-
return PythonMappingProxies.GetReverseIterator(sequence, span);
376-
}
377-
378-
if (sequence is PythonManagedObjectValue instance)
379-
{
380-
if (
381-
UserObjectProtocols.TryGetSpecialMethod(
382-
instance,
383-
"__reversed__",
384-
out var method,
385-
out _
386-
)
387-
)
388-
{
389-
var iterator = UserObjectProtocols.Dispatcher!.Invoke(method, [], span);
390-
return ManagedObjectProtocols.GetIterator(iterator, span);
391-
}
392-
393-
if (
394-
UserObjectProtocols.DefinesSpecialMethod(instance, "__len__")
395-
&& UserObjectProtocols.DefinesSpecialMethod(instance, "__getitem__")
396-
)
397-
{
398-
var remaining = ManagedObjectProtocols.GetLength(instance, span);
399-
return new PythonIteratorValue(
400-
new PythonUserIteratorSourceValue(() =>
401-
remaining > 0
402-
? (
403-
true,
404-
ManagedObjectProtocols.GetItem(
405-
instance,
406-
PythonWholeNumberValue.Create(--remaining),
407-
span
408-
)
409-
)
410-
: (false, PythonNoneValue.Instance)
411-
),
412-
-1
413-
);
414-
}
415-
416-
throw NotReversible(sequence, span);
417-
}
418-
419-
List<PythonValue> values = sequence switch
420-
{
421-
PythonListValue list => [.. list.Elements],
422-
PythonTupleValue tuple => [.. tuple.Elements],
423-
PythonTextValue text =>
424-
[
425-
.. text
426-
.Value.EnumerateRunes()
427-
.Select(rune => (PythonValue)new PythonTextValue(rune.ToString())),
428-
],
429-
PythonByteSequenceValue bytes =>
430-
[
431-
.. bytes.Value.Select(item => (PythonValue)PythonWholeNumberValue.Create(item)),
432-
],
433-
PythonRangeValue => ManagedObjectProtocols.MaterializeValues(sequence, span),
434-
_ => throw NotReversible(sequence, span),
435-
};
436-
values.Reverse();
437-
return new PythonIteratorValue(new PythonListValue(values), -1);
367+
return PythonReverseIterators.Create(arguments[0], span);
438368
}
439369

440-
private static PythonRuntimeException NotReversible(PythonValue value, TextSpan span) =>
441-
ManagedObjectProtocols.Fault(
442-
"DPY4003",
443-
$"'{ManagedObjectProtocols.GetTypeName(value)}' object is not reversible",
444-
span,
445-
"TypeError"
446-
);
447-
448370
// ----------------------------------------------------------------------------
449371
// object
450372
// ----------------------------------------------------------------------------

‎src/DotPython.Runtime.Managed/Execution/PythonBuiltinTypes.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ internal static string GetRuntimeTypeName(PythonValue value) =>
173173
{
174174
PythonBuiltinTypeValue => "type",
175175
PythonSuperProxyValue => "super",
176+
PythonIteratorValue { Iterable: PythonReverseIteratorSourceValue reverse } =>
177+
reverse.TypeName,
176178
PythonIteratorValue { Iterable: PythonEnumerateSourceValue } => "enumerate",
177179
PythonIteratorValue { Iterable: PythonZipSourceValue } => "zip",
178180
PythonIteratorValue { Iterable: PythonMapSourceValue } => "map",

‎src/DotPython.Runtime.Managed/Execution/PythonLengthHints.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ internal static long GetLengthHint(PythonValue value, TextSpan span, long defaul
7979
internal static bool SupportsIterator(PythonIteratorValue iterator) =>
8080
iterator.Iterable
8181
is PythonSequenceIteratorSourceValue
82+
or PythonReverseIteratorSourceValue
8283
or PythonListValue
8384
or PythonTupleValue
8485
or PythonRangeValue
@@ -94,6 +95,11 @@ private static PythonValue GetIteratorHint(PythonIteratorValue iterator, TextSpa
9495
return PythonWholeNumberValue.Create(0);
9596
return iterator.Iterable switch
9697
{
98+
PythonReverseIteratorSourceValue reverse => PythonReverseIterators.GetHint(
99+
iterator,
100+
reverse,
101+
span
102+
),
97103
PythonListValue list => PythonWholeNumberValue.Create(
98104
Math.Max(0, list.Elements.Count - iterator.Index)
99105
),
@@ -138,6 +144,15 @@ private static PythonValue GetSequenceIteratorHint(PythonIteratorValue iterator,
138144
return PythonWholeNumberValue.Create(Math.Max(0, length - source.NextIndex));
139145
}
140146

147+
internal static long GetSequenceLength(PythonValue value, TextSpan span) =>
148+
TryGetLength(value, span, out var length)
149+
? length
150+
: throw Error(
151+
$"object of type '{ManagedObjectProtocols.GetTypeName(value)}' has no len()",
152+
"TypeError",
153+
span
154+
);
155+
141156
private static bool TryGetLength(PythonValue value, TextSpan span, out long length)
142157
{
143158
if (ManagedObjectProtocols.GetManagedType(value) is not null)

‎src/DotPython.Runtime.Managed/Execution/PythonMappingProxies.cs‎

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ internal static PythonValue GetAttribute(
6161
TextSpan span
6262
)
6363
{
64+
if (name == "__reversed__")
65+
return new PythonBoundMethodValue(name, proxy, PythonReverseIterators.Method);
6466
if (name is not ("get" or "keys" or "values" or "items" or "copy"))
6567
{
6668
throw ManagedObjectProtocols.Fault(
@@ -86,64 +88,6 @@ TextSpan span
8688
);
8789
}
8890

89-
internal static PythonIteratorValue GetReverseIterator(PythonValue value, TextSpan span)
90-
{
91-
value = Unwrap(value);
92-
var dictionary = value switch
93-
{
94-
PythonDictionaryValue mapping => mapping,
95-
PythonDictionaryViewValue view => view.Dictionary,
96-
_ => null,
97-
};
98-
if (dictionary is null)
99-
{
100-
if (ManagedObjectProtocols.TryGetSpecialMethod(value, "__reversed__", out var method))
101-
{
102-
return ManagedObjectProtocols.GetIterator(
103-
UserObjectProtocols.Dispatcher!.Invoke(method, [], span),
104-
span
105-
);
106-
}
107-
throw ManagedObjectProtocols.Fault(
108-
"DPY4015",
109-
$"'{ManagedObjectProtocols.GetTypeName(value)}' object is not reversible",
110-
span,
111-
"TypeError"
112-
);
113-
}
114-
var version = dictionary.SizeVersion;
115-
var index = dictionary.Items.Count;
116-
var exhausted = false;
117-
var kind = value is PythonDictionaryViewValue dictionaryView
118-
? dictionaryView.Kind
119-
: "dict_keys";
120-
return new PythonIteratorValue(
121-
new PythonUserIteratorSourceValue(() =>
122-
{
123-
if (exhausted)
124-
{
125-
return (false, PythonNoneValue.Instance);
126-
}
127-
if (dictionary.SizeVersion != version)
128-
{
129-
throw ManagedObjectProtocols.Fault(
130-
"DPY4016",
131-
"dictionary changed size during iteration",
132-
span,
133-
"RuntimeError"
134-
);
135-
}
136-
if (index > 0)
137-
{
138-
return (true, ViewItem(dictionary.Items[--index], kind));
139-
}
140-
exhausted = true;
141-
return (false, PythonNoneValue.Instance);
142-
}),
143-
-1
144-
);
145-
}
146-
14791
internal static PythonValue ViewItem(PythonDictionaryItemValue item, string kind) =>
14892
kind switch
14993
{

0 commit comments

Comments
 (0)