Skip to content

Commit f0daa88

Browse files
csabellaterryjreedy
authored andcommitted
bpo-32940: IDLE: Simplify StringTranslatePseudoMapping in pyparse (GH-5862)
The new code also runs faster.
1 parent 45ab51c commit f0daa88

File tree

3 files changed

+32
-89
lines changed

3 files changed

+32
-89
lines changed

‎Lib/idlelib/idle_test/test_pyparse.py‎

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,20 @@
88
from idlelib import pyparse
99

1010

11-
class StringTranslatePseudoMappingTest(unittest.TestCase):
12-
13-
@classmethod
14-
def setUpClass(cls):
15-
whitespace_chars = ' \t\n\r'
16-
cls.preserve_dict = {ord(c): ord(c) for c in whitespace_chars}
17-
cls.default = ord('x')
18-
cls.mapping = pyparse.StringTranslatePseudoMapping(
19-
cls.preserve_dict, default_value=ord('x'))
20-
21-
@classmethod
22-
def tearDownClass(cls):
23-
del cls.preserve_dict, cls.default, cls.mapping
24-
25-
def test__init__(self):
26-
m = self.mapping
27-
self.assertEqual(m._non_defaults, self.preserve_dict)
28-
self.assertEqual(m._default_value, self.default)
29-
30-
def test__get_item__(self):
31-
self.assertEqual(self.mapping[ord('\t')], ord('\t'))
32-
self.assertEqual(self.mapping[ord('a')], self.default)
33-
34-
def test__len__(self):
35-
self.assertEqual(len(self.mapping), len(self.preserve_dict))
36-
37-
def test__iter__(self):
38-
count = 0
39-
for key, value in self.mapping.items():
40-
self.assertIn(key, self.preserve_dict)
41-
count += 1
42-
self.assertEqual(count, len(self.mapping))
43-
44-
def test_get(self):
45-
self.assertEqual(self.mapping.get(ord('\t')), ord('\t'))
46-
self.assertEqual(self.mapping.get('a'), self.default)
47-
# Default is a parameter, but it isn't used.
48-
self.assertEqual(self.mapping.get('a', default=500), self.default)
11+
class ParseMapTest(unittest.TestCase):
12+
13+
def test_parsemap(self):
14+
keepwhite = {ord(c): ord(c) for c in ' \t\n\r'}
15+
mapping = pyparse.ParseMap(keepwhite)
16+
self.assertEqual(mapping[ord('\t')], ord('\t'))
17+
self.assertEqual(mapping[ord('a')], ord('x'))
18+
self.assertEqual(mapping[1000], ord('x'))
19+
20+
def test_trans(self):
21+
# trans is the production instance of ParseMap, used in _study1
22+
parser = pyparse.Parser(4, 4)
23+
self.assertEqual('\t a([{b}])b"c\'d\n'.translate(pyparse.trans),
24+
'xxx(((x)))x"x\'x\n')
4925

5026

5127
class PyParseTest(unittest.TestCase):
@@ -152,10 +128,6 @@ def test_set_lo(self):
152128
p.set_lo(44)
153129
self.assertEqual(p.code, code[44:])
154130

155-
def test_tran(self):
156-
self.assertEqual('\t a([{b}])b"c\'d\n'.translate(self.parser._tran),
157-
'xxx(((x)))x"x\'x\n')
158-
159131
def test_study1(self):
160132
eq = self.assertEqual
161133
p = self.parser

‎Lib/idlelib/pyparse.py‎

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Define partial Python code Parser used by editor and hyperparser.
22
3-
Instances of StringTranslatePseudoMapping are used with str.translate.
3+
Instances of ParseMap are used with str.translate.
44
55
The following bound search and match functions are defined:
66
_synchre - start of popular statement;
@@ -10,7 +10,6 @@
1010
_closere - line that must be followed by dedent.
1111
_chew_ordinaryre - non-special characters.
1212
"""
13-
from collections.abc import Mapping
1413
import re
1514
import sys
1615

@@ -101,46 +100,27 @@
101100
""", re.VERBOSE).match
102101

103102

104-
class StringTranslatePseudoMapping(Mapping):
105-
r"""Utility class to be used with str.translate()
103+
class ParseMap(dict):
104+
r"""Dict subclass that maps anything not in dict to 'x'.
106105
107-
This Mapping class wraps a given dict. When a value for a key is
108-
requested via __getitem__() or get(), the key is looked up in the
109-
given dict. If found there, the value from the dict is returned.
110-
Otherwise, the default value given upon initialization is returned.
106+
This is designed to be used with str.translate in study1.
107+
Anything not specifically mapped otherwise becomes 'x'.
108+
Example: replace everything except whitespace with 'x'.
111109
112-
This allows using str.translate() to make some replacements, and to
113-
replace all characters for which no replacement was specified with
114-
a given character instead of leaving them as-is.
115-
116-
For example, to replace everything except whitespace with 'x':
117-
118-
>>> whitespace_chars = ' \t\n\r'
119-
>>> preserve_dict = {ord(c): ord(c) for c in whitespace_chars}
120-
>>> mapping = StringTranslatePseudoMapping(preserve_dict, ord('x'))
121-
>>> text = "a + b\tc\nd"
122-
>>> text.translate(mapping)
110+
>>> keepwhite = ParseMap((ord(c), ord(c)) for c in ' \t\n\r')
111+
>>> "a + b\tc\nd".translate(keepwhite)
123112
'x x x\tx\nx'
124113
"""
125-
def __init__(self, non_defaults, default_value):
126-
self._non_defaults = non_defaults
127-
self._default_value = default_value
128-
129-
def _get(key, _get=non_defaults.get, _default=default_value):
130-
return _get(key, _default)
131-
self._get = _get
114+
# Calling this triples access time; see bpo-32940
115+
def __missing__(self, key):
116+
return 120 # ord('x')
132117

133-
def __getitem__(self, item):
134-
return self._get(item)
135118

136-
def __len__(self):
137-
return len(self._non_defaults)
138-
139-
def __iter__(self):
140-
return iter(self._non_defaults)
141-
142-
def get(self, key, default=None):
143-
return self._get(key)
119+
# Map all ascii to 120 to avoid __missing__ call, then replace some.
120+
trans = ParseMap.fromkeys(range(128), 120)
121+
trans.update((ord(c), ord('(')) for c in "({[") # open brackets => '(';
122+
trans.update((ord(c), ord(')')) for c in ")}]") # close brackets => ')'.
123+
trans.update((ord(c), ord(c)) for c in "\"'\\\n#") # Keep these.
144124

145125

146126
class Parser:
@@ -224,16 +204,6 @@ def set_lo(self, lo):
224204
if lo > 0:
225205
self.code = self.code[lo:]
226206

227-
# Build a translation table to map uninteresting chars to 'x', open
228-
# brackets to '(', close brackets to ')' while preserving quotes,
229-
# backslashes, newlines and hashes. This is to be passed to
230-
# str.translate() in _study1().
231-
_tran = {}
232-
_tran.update((ord(c), ord('(')) for c in "({[")
233-
_tran.update((ord(c), ord(')')) for c in ")}]")
234-
_tran.update((ord(c), ord(c)) for c in "\"'\\\n#")
235-
_tran = StringTranslatePseudoMapping(_tran, default_value=ord('x'))
236-
237207
def _study1(self):
238208
"""Find the line numbers of non-continuation lines.
239209
@@ -250,7 +220,7 @@ def _study1(self):
250220
# uninteresting characters. This can cut the number of chars
251221
# by a factor of 10-40, and so greatly speed the following loop.
252222
code = self.code
253-
code = code.translate(self._tran)
223+
code = code.translate(trans)
254224
code = code.replace('xxxxxxxx', 'x')
255225
code = code.replace('xxxx', 'x')
256226
code = code.replace('xx', 'x')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Simplify and rename StringTranslatePseudoMapping in pyparse.

0 commit comments

Comments
 (0)