Skip to content

Commit 8bda517

Browse files
authored
Merge pull request #1003 from mathics/assignnamepattern
Extends Protect and Unprotect to include string patterns
2 parents 61d82d7 + e0c16a8 commit 8bda517

File tree

4 files changed

+169
-46
lines changed

4 files changed

+169
-46
lines changed

mathics/builtin/assignment.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@
1717
from mathics.core.definitions import PyMathicsLoadException
1818

1919

20+
def repl_pattern_by_symbol(expr):
21+
leaves = expr.get_leaves()
22+
if len(leaves) == 0:
23+
return expr
24+
25+
headname = expr.get_head_name()
26+
if headname == "System`Pattern":
27+
return leaves[0]
28+
29+
changed = False
30+
newleaves = []
31+
for leave in leaves:
32+
l = repl_pattern_by_symbol(leave)
33+
if not(l is leave):
34+
changed = True
35+
newleaves.append(l)
36+
if changed:
37+
return Expression(headname,*newleaves)
38+
else:
39+
return expr
40+
41+
2042
def get_symbol_list(list, error_callback):
2143
if list.has_form('List', None):
2244
list = list.leaves
@@ -38,6 +60,13 @@ def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False):
3860
name = lhs.get_head_name()
3961
lhs._format_cache = None
4062

63+
if name == "System`Pattern":
64+
lhsleaves= lhs.get_leaves()
65+
lhs = lhsleaves[1]
66+
rulerepl = (lhsleaves[0], repl_pattern_by_symbol(lhs))
67+
rhs, status = rhs.apply_rules([Rule(*rulerepl)], evaluation)
68+
name = lhs.get_head_name()
69+
4170
if name in system_symbols('OwnValues', 'DownValues', 'SubValues',
4271
'UpValues', 'NValues', 'Options',
4372
'DefaultValues', 'Attributes', 'Messages'):
@@ -220,7 +249,7 @@ def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False):
220249
elif lhs_name == 'System`$ContextPath':
221250
currContext = evaluation.definitions.get_current_context()
222251
context_path = [s.get_string_value() for s in rhs.get_leaves()]
223-
context_path = [s if (s is None or s[0]!="`") else currContext +s for s in context_path]
252+
context_path = [s if (s is None or s[0]!="`") else currContext[:-1] +s for s in context_path]
224253
if rhs.has_form('List', None) and all(valid_context_name(s) for s in context_path):
225254
evaluation.definitions.set_context_path(context_path)
226255
ignore_protection = True
@@ -1037,15 +1066,27 @@ def do_clear(self, definition):
10371066

10381067
def apply(self, symbols, evaluation):
10391068
'%(name)s[symbols___]'
1040-
1041-
for symbol in symbols.get_sequence():
1069+
if isinstance(symbols ,Symbol):
1070+
symbols = [symbols]
1071+
elif isinstance(symbols, Expression):
1072+
symbols = symbols.get_leaves()
1073+
elif isinstance(symbols ,String):
1074+
symbols = [symbols]
1075+
else:
1076+
symbols = symbols.get_sequence()
1077+
1078+
for symbol in symbols:
10421079
if isinstance(symbol, Symbol):
10431080
names = [symbol.get_name()]
10441081
else:
10451082
pattern = symbol.get_string_value()
10461083
if not pattern:
10471084
evaluation.message('Clear', 'ssym', symbol)
10481085
continue
1086+
if pattern[0] == "`":
1087+
pattern = (evaluation.definitions.get_current_context()
1088+
+ pattern[1:])
1089+
10491090
names = evaluation.definitions.get_matching_names(pattern)
10501091
for name in names:
10511092
attributes = evaluation.definitions.get_attributes(name)

mathics/builtin/attributes.py

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111

1212
from mathics.builtin.base import Predefined, Builtin
13-
from mathics.core.expression import Symbol, Expression
13+
from mathics.builtin.evaluation import Sequence
14+
from mathics.core.expression import Expression, Symbol, String
1415
from mathics.builtin.assignment import get_symbol_list
1516

1617

@@ -140,8 +141,11 @@ def apply(self, symbols, attributes, evaluation):
140141
class Protect(Builtin):
141142
"""
142143
<dl>
143-
<dt>'Protect'[$s1$, $s2$, ...]
144-
<dd>sets the attribute 'Protected' for the symbols $si$.
144+
<dt>'Protect'[$s1$, $s2$, ...]
145+
<dd>sets the attribute 'Protected' for the symbols $si$.
146+
147+
<dt>'Protect'[$str1$, $str2$, ...]
148+
<dd>protects all symbols whose names textually match $stri$.
145149
</dl>
146150
147151
>> A = {1, 2, 3};
@@ -153,26 +157,96 @@ class Protect(Builtin):
153157
"""
154158

155159
attributes = ('HoldAll',)
156-
157-
rules = {
158-
'Protect[symbols__]': 'SetAttributes[{symbols}, Protected]',
160+
messages = {
161+
'ssym': "`1` is not a symbol or a string.",
159162
}
160163

164+
def apply(self, symbols, evaluation):
165+
"Protect[symbols___]"
166+
protected = Symbol("System`Protected")
167+
items = []
168+
169+
if isinstance(symbols ,Symbol):
170+
symbols = [symbols]
171+
elif isinstance(symbols, String):
172+
symbols = [symbols]
173+
elif isinstance(symbols, Expression):
174+
if symbols.get_head_name() in ("System`Sequence", "System`List"):
175+
symbols = symbols.get_leaves()
176+
else:
177+
evaluation.message('Protect', 'ssym', symbol)
178+
return Symbol("Null")
179+
180+
for symbol in symbols:
181+
if isinstance(symbol, Symbol):
182+
items.append(symbol)
183+
else:
184+
pattern = symbol.get_string_value()
185+
if not pattern or pattern=="":
186+
evaluation.message('Protect', 'ssym', symbol)
187+
continue
188+
189+
if pattern[0] == "`":
190+
pattern = evaluation.definitions.get_current_context() + pattern[1:]
191+
names = evaluation.definitions.get_matching_names(pattern)
192+
for defn in names:
193+
symbol = Symbol(defn)
194+
if not 'System`Locked' in evaluation.definitions.get_attributes(defn):
195+
items.append(symbol)
196+
197+
Expression("SetAttributes", Expression("List", *items), protected).evaluate(evaluation)
198+
return Symbol('Null')
199+
161200

162201
class Unprotect(Builtin):
163202
"""
164203
<dl>
165-
<dt>'Unprotect'[$s1$, $s2$, ...]
166-
<dd>removes the attribute 'Protected' for the symbols $si$.
204+
<dt>'Unprotect'[$s1$, $s2$, ...]
205+
<dd>removes the attribute 'Protected' for the symbols $si$.
206+
207+
<dt>'Unprotect'[$str$]
208+
<dd>unprotects symbols whose names textually match $str$.
167209
</dl>
168210
"""
169211

170212
attributes = ('HoldAll',)
171-
172-
rules = {
173-
'Unprotect[symbols__]': 'ClearAttributes[{symbols}, Protected]',
213+
messages = {
214+
'ssym': "`1` is not a symbol or a string.",
174215
}
175216

217+
def apply(self, symbols, evaluation):
218+
"Unprotect[symbols___]"
219+
protected = Symbol("System`Protected")
220+
items = []
221+
if isinstance(symbols ,Symbol):
222+
symbols = [symbols]
223+
elif isinstance(symbols, Expression):
224+
symbols = symbols.get_leaves()
225+
elif isinstance(symbols ,String):
226+
symbols = [symbols]
227+
else:
228+
symbols = symbols.get_sequence()
229+
230+
for symbol in symbols:
231+
if isinstance(symbol, Symbol):
232+
items.append(symbol)
233+
else:
234+
pattern = symbol.get_string_value()
235+
if not pattern or pattern=="":
236+
evaluation.message('Unprotect', 'ssym', symbol)
237+
continue
238+
239+
if pattern[0] == "`":
240+
pattern = evaluation.definitions.get_current_context() + pattern[1:]
241+
names = evaluation.definitions.get_matching_names(pattern)
242+
for defn in names:
243+
symbol = Symbol(defn)
244+
if not 'System`Locked' in evaluation.definitions.get_attributes(defn):
245+
items.append(symbol)
246+
247+
Expression("ClearAttributes", Expression("List", *items), protected).evaluate(evaluation)
248+
return Symbol('Null')
249+
176250

177251
class Protected(Predefined):
178252
"""

mathics/builtin/system.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import os
1010
import platform
1111
import sys
12+
import re
1213

1314
from mathics.core.expression import Expression, Integer, String, Symbol, strip_context
1415
from mathics.builtin.base import Builtin, Predefined
1516
from mathics import version_string
17+
from mathics.builtin.strings import StringExpression, to_regex
1618

1719

1820
class Aborted(Predefined):
@@ -194,8 +196,12 @@ class Names(Builtin):
194196

195197
def apply(self, pattern, evaluation):
196198
"Names[pattern_]"
199+
headname = pattern.get_head_name()
200+
if headname == "System`StringExpression":
201+
pattern = re.compile(to_regex(pattern, evaluation))
202+
else:
203+
pattern = pattern.get_string_value()
197204

198-
pattern = pattern.get_string_value()
199205
if pattern is None:
200206
return
201207

mathics/core/definitions.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
)
2222
from mathics.core.characters import letters, letterlikes
2323

24-
24+
type_compiled_pattern = type(re.compile("a.a"))
2525
names_wildcards = "@*"
2626
base_names_pattern = r"((?![0-9])([0-9${0}{1}{2}])+)".format(
2727
letters, letterlikes, names_wildcards
@@ -64,6 +64,7 @@ def __init__(
6464
self.lookup_cache = {}
6565
self.proxy = defaultdict(set)
6666
self.now = 0 # increments whenever something is updated
67+
self._packages = []
6768

6869
if add_builtin:
6970
from mathics.builtin import modules, contribute
@@ -312,37 +313,38 @@ def get_matching_names(self, pattern) -> typing.List[str]:
312313
which aren't uppercase letters. In the context pattern, both
313314
'*' and '@' match context marks.
314315
"""
315-
316-
if re.match(full_names_pattern, pattern) is None:
317-
# The pattern contained characters which weren't allowed
318-
# in symbols and aren't valid wildcards. Hence, the
319-
# pattern can't match any symbols.
320-
return []
321-
322-
# If we get here, there aren't any regexp metacharacters in
323-
# the pattern.
324-
325-
if "`" in pattern:
326-
ctx_pattern, short_pattern = pattern.rsplit("`", 1)
327-
ctx_pattern = (
328-
(ctx_pattern + "`")
329-
.replace("@", "[^A-Z`]+")
330-
.replace("*", ".*")
331-
.replace("$", r"\$")
332-
)
316+
if isinstance(pattern, type_compiled_pattern):
317+
regex = pattern
333318
else:
334-
short_pattern = pattern
335-
# start with a group matching the accessible contexts
336-
ctx_pattern = "(?:%s)" % "|".join(
337-
re.escape(c) for c in self.get_accessible_contexts()
338-
)
339-
340-
short_pattern = (
341-
short_pattern.replace("@", "[^A-Z]+")
342-
.replace("*", "[^`]*")
343-
.replace("$", r"\$")
344-
)
345-
regex = re.compile("^" + ctx_pattern + short_pattern + "$")
319+
if re.match(full_names_pattern, pattern) is None:
320+
# The pattern contained characters which weren't allowed
321+
# in symbols and aren't valid wildcards. Hence, the
322+
# pattern can't match any symbols.
323+
return []
324+
325+
# If we get here, there aren't any regexp metacharacters in
326+
# the pattern.
327+
328+
if '`' in pattern:
329+
ctx_pattern, short_pattern = pattern.rsplit('`', 1)
330+
if ctx_pattern == "":
331+
ctx_pattern="System`"
332+
else:
333+
ctx_pattern = ((ctx_pattern + '`')
334+
.replace('@', '[^A-Z`]+')
335+
.replace('*', '.*')
336+
.replace('$', r'\$'))
337+
else:
338+
short_pattern = pattern
339+
# start with a group matching the accessible contexts
340+
ctx_pattern = "(?:%s)" % "|".join(
341+
re.escape(c) for c in self.get_accessible_contexts())
342+
343+
short_pattern = (short_pattern
344+
.replace('@', '[^A-Z]+')
345+
.replace('*', '[^`]*')
346+
.replace('$', r'\$'))
347+
regex = re.compile('^' + ctx_pattern + short_pattern + '$')
346348

347349
return [name for name in self.get_names() if regex.match(name)]
348350

0 commit comments

Comments
 (0)