1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4import pickle
5
6import os
7import base64
8import re
9import bisect
10
11from collections import defaultdict
12
13import typing
14
15from mathics.core.expression import (
16    Expression,
17    Symbol,
18    String,
19    fully_qualified_symbol_name,
20    strip_context,
21)
22from mathics_scanner.tokeniser import full_names_pattern
23
24type_compiled_pattern = type(re.compile("a.a"))
25
26
27def get_file_time(file) -> float:
28    try:
29        return os.stat(file).st_mtime
30    except OSError:
31        return 0
32
33
34def valuesname(name) -> str:
35    " 'NValues' -> 'n' "
36
37    assert name.startswith("System`"), name
38    if name == "System`Messages":
39        return "messages"
40    else:
41        return name[7:-6].lower()
42
43
44def autoload_files(defs, root_dir_path: str, autoload_dir):
45    from mathics.core.evaluation import Evaluation
46
47    # Load symbols from the autoload folder
48    for root, dirs, files in os.walk(os.path.join(root_dir_path, autoload_dir)):
49        for path in [os.path.join(root, f) for f in files if f.endswith(".m")]:
50            Expression("Get", String(path)).evaluate(Evaluation(defs))
51
52    # Move any user definitions created by autoloaded files to
53    # builtins, and clear out the user definitions list. This
54    # means that any autoloaded definitions become shared
55    # between users and no longer disappear after a Quit[].
56    #
57    # Autoloads that accidentally define a name in Global`
58    # could cause confusion, so check for this.
59    #
60    for name in defs.user:
61        if name.startswith("Global`"):
62            raise ValueError("autoload defined %s." % name)
63
64
65class PyMathicsLoadException(Exception):
66    def __init__(self, module):
67        self.name = module + " is not a valid pymathics module"
68        self.module = module
69
70
71class Definitions(object):
72    def __init__(
73        self, add_builtin=False, builtin_filename=None, extension_modules=[]
74    ) -> None:
75        super(Definitions, self).__init__()
76        self.builtin = {}
77        self.user = {}
78        self.pymathics = {}
79        self.definitions_cache = {}
80        self.lookup_cache = {}
81        self.proxy = defaultdict(set)
82        self.now = 0  # increments whenever something is updated
83        self._packages = []
84
85        if add_builtin:
86            from mathics.builtin import modules, contribute
87            from mathics.core.evaluation import Evaluation
88            from mathics.settings import ROOT_DIR
89
90            loaded = False
91            if builtin_filename is not None:
92                builtin_dates = [get_file_time(module.__file__) for module in modules]
93                builtin_time = max(builtin_dates)
94                if get_file_time(builtin_filename) > builtin_time:
95                    builtin_file = open(builtin_filename, "rb")
96                    self.builtin = pickle.load(builtin_file)
97                    loaded = True
98            if not loaded:
99                contribute(self)
100                for module in extension_modules:
101                    try:
102                        self.load_pymathics_module(module, remove_on_quit=False)
103                    except PyMathicsLoadException:
104                        raise
105                    except ImportError:
106                        raise
107
108                if builtin_filename is not None:
109                    builtin_file = open(builtin_filename, "wb")
110                    pickle.dump(self.builtin, builtin_file, -1)
111
112            autoload_files(self, ROOT_DIR, "autoload")
113
114            # Move any user definitions created by autoloaded files to
115            # builtins, and clear out the user definitions list. This
116            # means that any autoloaded definitions become shared
117            # between users and no longer disappear after a Quit[].
118            #
119            # Autoloads that accidentally define a name in Global`
120            # could cause confusion, so check for this.
121            #
122            for name in self.user:
123                if name.startswith("Global`"):
124                    raise ValueError("autoload defined %s." % name)
125
126            self.builtin.update(self.user)
127            self.user = {}
128            self.clear_cache()
129
130    def load_pymathics_module(self, module, remove_on_quit=True):
131        """
132        Loads Mathics builtin objects and their definitions
133        from an external Python module in the pymathics module namespace.
134        """
135        import importlib
136        from mathics.builtin import is_builtin, builtins_by_module, Builtin
137
138        # Ensures that the pymathics module be reloaded
139        import sys
140
141        if module in sys.modules:
142            loaded_module = importlib.reload(sys.modules[module])
143        else:
144            loaded_module = importlib.import_module(module)
145
146        builtins_by_module[loaded_module.__name__] = []
147        vars = set(
148            loaded_module.__all__
149            if hasattr(loaded_module, "__all__")
150            else dir(loaded_module)
151        )
152
153        newsymbols = {}
154        if not ("pymathics_version_data" in vars):
155            raise PyMathicsLoadException(module)
156        for name in vars - set(("pymathics_version_data", "__version__")):
157            var = getattr(loaded_module, name)
158            if (
159                hasattr(var, "__module__")
160                and is_builtin(var)
161                and not name.startswith("_")
162                and var.__module__[: len(loaded_module.__name__)]
163                == loaded_module.__name__
164            ):  # nopep8
165                instance = var(expression=False)
166                if isinstance(instance, Builtin):
167                    if not var.context:
168                        var.context = "Pymathics`"
169                    symbol_name = instance.get_name()
170                    builtins_by_module[loaded_module.__name__].append(instance)
171                    newsymbols[symbol_name] = instance
172
173        for name in newsymbols:
174            luname = self.lookup_name(name)
175            self.user.pop(name, None)
176
177        for name, item in newsymbols.items():
178            if name != "System`MakeBoxes":
179                item.contribute(self, is_pymodule=True)
180
181        onload = loaded_module.pymathics_version_data.get("onload", None)
182        if onload:
183            onload(self)
184
185        return loaded_module
186
187    def clear_pymathics_modules(self):
188        from mathics.builtin import builtins, builtins_by_module
189
190        for key in list(builtins_by_module.keys()):
191            if not key.startswith("mathics."):
192                del builtins_by_module[key]
193        for key in self.pymathics:
194            del self.pymathics[key]
195
196        self.pymathics = {}
197        return None
198
199    def clear_cache(self, name=None):
200        # the definitions cache (self.definitions_cache) caches (incomplete and complete) names -> Definition(),
201        # e.g. "xy" -> d and "MyContext`xy" -> d. we need to clear this cache if a Definition() changes (which
202        # would happen if a Definition is combined from a builtin and a user definition and some content in the
203        # user definition is updated) or if the lookup rules change and we could end up at a completely different
204        # Definition.
205
206        # the lookup cache (self.lookup_cache) caches what lookup_name() does. we only need to update this if some
207        # change happens that might change the result lookup_name() calculates. we do not need to change it if a
208        # Definition() changes.
209
210        # self.proxy keeps track of all the names we cache. if we need to clear the caches for only one name, e.g.
211        # 'MySymbol', then we need to be able to look up all the entries that might be related to it, e.g. 'MySymbol',
212        # 'A`MySymbol', 'C`A`MySymbol', and so on. proxy identifies symbols using their stripped name and thus might
213        # give us symbols in other contexts that are actually not affected. still, this is a safe solution.
214
215        if name is None:
216            self.definitions_cache = {}
217            self.lookup_cache = {}
218            self.proxy = defaultdict(set)
219        else:
220            definitions_cache = self.definitions_cache
221            lookup_cache = self.lookup_cache
222            tail = strip_context(name)
223            for k in self.proxy.pop(tail, []):
224                definitions_cache.pop(k, None)
225                lookup_cache.pop(k, None)
226
227    def clear_definitions_cache(self, name) -> None:
228        definitions_cache = self.definitions_cache
229        tail = strip_context(name)
230        for k in self.proxy.pop(tail, []):
231            definitions_cache.pop(k, None)
232
233    def has_changed(self, maximum, symbols):
234        # timestamp for the most recently changed part of a given expression.
235        for name in symbols:
236            symb = self.get_definition(name, only_if_exists=True)
237            if symb is None:
238                # symbol doesn't exist so it was never changed
239                pass
240            else:
241                changed = getattr(symb, "changed", None)
242                if changed is None:
243                    # must be system symbol
244                    symb.changed = 0
245                elif changed > maximum:
246                    return True
247
248        return False
249
250    def get_current_context(self):
251        # It's crucial to specify System` in this get_ownvalue() call,
252        # otherwise we'll end up back in this function and trigger
253        # infinite recursion.
254        context_rule = self.get_ownvalue("System`$Context")
255        context = context_rule.replace.get_string_value()
256        assert context is not None, "$Context somehow set to an invalid value"
257        return context
258
259    def get_context_path(self):
260        context_path_rule = self.get_ownvalue("System`$ContextPath")
261        context_path = context_path_rule.replace
262        assert context_path.has_form("System`List", None)
263        context_path = [c.get_string_value() for c in context_path.leaves]
264        assert not any([c is None for c in context_path])
265        return context_path
266
267    def set_current_context(self, context) -> None:
268        assert isinstance(context, str)
269        self.set_ownvalue("System`$Context", String(context))
270        self.clear_cache()
271
272    def set_context_path(self, context_path) -> None:
273        assert isinstance(context_path, list)
274        assert all([isinstance(c, str) for c in context_path])
275        self.set_ownvalue(
276            "System`$ContextPath",
277            Expression("System`List", *[String(c) for c in context_path]),
278        )
279        self.clear_cache()
280
281    def get_builtin_names(self):
282        return set(self.builtin)
283
284    def get_user_names(self):
285        return set(self.user)
286
287    def get_pymathics_names(self):
288        return set(self.pymathics)
289
290    def get_names(self):
291        return (
292            self.get_builtin_names()
293            | self.get_pymathics_names()
294            | self.get_user_names()
295        )
296
297    def get_accessible_contexts(self):
298        "Return the contexts reachable though $Context or $ContextPath."
299        accessible_ctxts = set(self.get_context_path())
300        accessible_ctxts.add(self.get_current_context())
301        return accessible_ctxts
302
303    def get_matching_names(self, pattern) -> typing.List[str]:
304        """
305        Return a list of the symbol names matching a string pattern.
306
307        A pattern containing a context mark (of the form
308        "ctx_pattern`short_pattern") matches symbols whose context and
309        short name individually match the two patterns. A pattern
310        without a context mark matches symbols accessible through
311        $Context and $ContextPath whose short names match the pattern.
312
313        '*' matches any sequence of symbol characters or an empty
314        string. '@' matches a non-empty sequence of symbol characters
315        which aren't uppercase letters. In the context pattern, both
316        '*' and '@' match context marks.
317        """
318        if isinstance(pattern, type_compiled_pattern):
319            regex = pattern
320        else:
321            if re.match(full_names_pattern, pattern) is None:
322                # The pattern contained characters which weren't allowed
323                # in symbols and aren't valid wildcards. Hence, the
324                # pattern can't match any symbols.
325                return []
326
327            # If we get here, there aren't any regexp metacharacters in
328            # the pattern.
329
330            if "`" in pattern:
331                ctx_pattern, short_pattern = pattern.rsplit("`", 1)
332                if ctx_pattern == "":
333                    ctx_pattern = "System`"
334                else:
335                    ctx_pattern = (
336                        (ctx_pattern + "`")
337                        .replace("@", "[^A-Z`]+")
338                        .replace("*", ".*")
339                        .replace("$", r"\$")
340                    )
341            else:
342                short_pattern = pattern
343                # start with a group matching the accessible contexts
344                ctx_pattern = "(?:%s)" % "|".join(
345                    re.escape(c) for c in self.get_accessible_contexts()
346                )
347
348            short_pattern = (
349                short_pattern.replace("@", "[^A-Z]+")
350                .replace("*", "[^`]*")
351                .replace("$", r"\$")
352            )
353            regex = re.compile("^" + ctx_pattern + short_pattern + "$")
354
355        return [name for name in self.get_names() if regex.match(name)]
356
357    def lookup_name(self, name) -> str:
358        """
359        Determine the full name (including context) for a symbol name.
360
361        - If the name begins with a context mark, it's in the context
362          given by $Context.
363        - Otherwise, if it contains a context mark, it's already fully
364          specified.
365        - Otherwise, it doesn't contain a context mark: try $Context,
366          then each element of $ContextPath, taking the first existing
367          symbol.
368        - Otherwise, it's a new symbol in $Context.
369        """
370
371        cached = self.lookup_cache.get(name, None)
372        if cached is not None:
373            return cached
374
375        assert isinstance(name, str)
376
377        # Bail out if the name we're being asked to look up is already
378        # fully qualified.
379        if fully_qualified_symbol_name(name):
380            return name
381
382        current_context = self.get_current_context()
383
384        if "`" in name:
385            if name.startswith("`"):
386                return current_context + name.lstrip("`")
387            return name
388
389        with_context = current_context + name
390        # if not self.have_definition(with_context):
391        for ctx in self.get_context_path():
392            n = ctx + name
393            if self.have_definition(n):
394                return n
395        return with_context
396
397    def get_package_names(self) -> typing.List[str]:
398        packages = self.get_ownvalue("System`$Packages")
399        packages = packages.replace
400        assert packages.has_form("System`List", None)
401        packages = [c.get_string_value() for c in packages.leaves]
402        return packages
403
404        # return sorted({name.split("`")[0] for name in self.get_names()})
405
406    def shorten_name(self, name_with_ctx) -> str:
407        if "`" not in name_with_ctx:
408            return name_with_ctx
409
410        def in_ctx(name, ctx):
411            return name.startswith(ctx) and "`" not in name[len(ctx) :]
412
413        if in_ctx(name_with_ctx, self.get_current_context()):
414            return name_with_ctx[len(self.get_current_context()) :]
415        for ctx in self.get_context_path():
416            if in_ctx(name_with_ctx, ctx):
417                return name_with_ctx[len(ctx) :]
418        return name_with_ctx
419
420    def have_definition(self, name) -> bool:
421        return self.get_definition(name, only_if_exists=True) is not None
422
423    def get_definition(self, name, only_if_exists=False) -> "Definition":
424        definition = self.definitions_cache.get(name, None)
425        if definition is not None:
426            return definition
427
428        original_name = name
429        name = self.lookup_name(name)
430        user = self.user.get(name, None)
431        pymathics = self.pymathics.get(name, None)
432        builtin = self.builtin.get(name, None)
433
434        candidates = [user] if user else []
435        builtin_instance = None
436        if pymathics:
437            builtin_instance = pymathics
438            candidates.append(pymathics)
439        if builtin:
440            candidates.append(builtin)
441            if builtin_instance is None:
442                builtin_instance = builtin
443
444        definition = candidates[0] if len(candidates) == 1 else None
445        if len(candidates) > 0 and not definition:
446            attributes = (
447                user.attributes
448                if user
449                else (
450                    pymathics.attributes
451                    if pymathics
452                    else (builtin.attributes if builtin else set())
453                )
454            )
455            upvalues = ([],)
456            messages = ([],)
457            nvalues = ([],)
458            defaultvalues = ([],)
459            options = {}
460            formatvalues = {
461                "": [],
462            }
463            # Merge definitions
464            its = [c for c in candidates]
465            while its:
466                curr = its.pop()
467                options.update(curr.options)
468                for form, rules in curr.formatvalues.items():
469                    if form in formatvalues:
470                        formatvalues[form].extend(rules)
471                    else:
472                        formatvalues[form] = rules
473            # Build the new definition
474            definition = Definition(
475                name=name,
476                ownvalues=sum((c.ownvalues for c in candidates), []),
477                downvalues=sum((c.downvalues for c in candidates), []),
478                subvalues=sum((c.subvalues for c in candidates), []),
479                upvalues=sum((c.upvalues for c in candidates), []),
480                formatvalues=formatvalues,
481                messages=sum((c.messages for c in candidates), []),
482                attributes=attributes,
483                options=options,
484                nvalues=sum((c.nvalues for c in candidates), []),
485                defaultvalues=sum((c.defaultvalues for c in candidates), []),
486                builtin=builtin_instance,
487            )
488
489        if definition is not None:
490            self.proxy[strip_context(original_name)].add(original_name)
491            self.definitions_cache[original_name] = definition
492            self.lookup_cache[original_name] = name
493        elif not only_if_exists:
494            definition = Definition(name=name)
495            if name[-1] != "`":
496                self.user[name] = definition
497
498        return definition
499
500    def get_attributes(self, name):
501        return self.get_definition(name).attributes
502
503    def get_ownvalues(self, name):
504        return self.get_definition(name).ownvalues
505
506    def get_downvalues(self, name):
507        return self.get_definition(name).downvalues
508
509    def get_subvalues(self, name):
510        return self.get_definition(name).subvalues
511
512    def get_upvalues(self, name):
513        return self.get_definition(name).upvalues
514
515    def get_formats(self, name, format=""):
516        formats = self.get_definition(name).formatvalues
517        result = formats.get(format, []) + formats.get("", [])
518        result.sort()
519        return result
520
521    def get_nvalues(self, name):
522        return self.get_definition(name).nvalues
523
524    def get_defaultvalues(self, name):
525        return self.get_definition(name).defaultvalues
526
527    def get_value(self, name, pos, pattern, evaluation):
528        assert isinstance(name, str)
529        assert "`" in name
530        rules = self.get_definition(name).get_values_list(valuesname(pos))
531        for rule in rules:
532            result = rule.apply(pattern, evaluation)
533            if result is not None:
534                return result
535
536    def get_user_definition(self, name, create=True) -> typing.Optional["Definition"]:
537        assert not isinstance(name, Symbol)
538
539        existing = self.user.get(name)
540        if existing:
541            return existing
542        else:
543            if not create:
544                return None
545            builtin = self.builtin.get(name)
546            if builtin:
547                attributes = builtin.attributes
548            else:
549                attributes = set()
550            self.user[name] = Definition(name=name, attributes=attributes)
551            self.clear_cache(name)
552            return self.user[name]
553
554    def mark_changed(self, definition) -> None:
555        self.now += 1
556        definition.changed = self.now
557
558    def reset_user_definition(self, name) -> None:
559        assert not isinstance(name, Symbol)
560        fullname = self.lookup_name(name)
561        del self.user[fullname]
562        self.clear_cache(fullname)
563        # TODO fix changed
564
565    def add_user_definition(self, name, definition) -> None:
566        assert not isinstance(name, Symbol)
567        self.mark_changed(definition)
568        fullname = self.lookup_name(name)
569        self.user[fullname] = definition
570        self.clear_cache(fullname)
571
572    def set_attribute(self, name, attribute) -> None:
573        definition = self.get_user_definition(self.lookup_name(name))
574        definition.attributes.add(attribute)
575        self.mark_changed(definition)
576        self.clear_definitions_cache(name)
577
578    def set_attributes(self, name, attributes) -> None:
579        definition = self.get_user_definition(self.lookup_name(name))
580        definition.attributes = set(attributes)
581        self.mark_changed(definition)
582        self.clear_definitions_cache(name)
583
584    def clear_attribute(self, name, attribute) -> None:
585        definition = self.get_user_definition(self.lookup_name(name))
586        if attribute in definition.attributes:
587            definition.attributes.remove(attribute)
588        self.mark_changed(definition)
589        self.clear_definitions_cache(name)
590
591    def add_rule(self, name, rule, position=None):
592        definition = self.get_user_definition(self.lookup_name(name))
593        if position is None:
594            result = definition.add_rule(rule)
595        else:
596            result = definition.add_rule_at(rule, position)
597        self.mark_changed(definition)
598        self.clear_definitions_cache(name)
599        return result
600
601    def add_format(self, name, rule, form="") -> None:
602        definition = self.get_user_definition(self.lookup_name(name))
603        if isinstance(form, tuple) or isinstance(form, list):
604            forms = form
605        else:
606            forms = [form]
607        for form in forms:
608            if form not in definition.formatvalues:
609                definition.formatvalues[form] = []
610            insert_rule(definition.formatvalues[form], rule)
611        self.mark_changed(definition)
612        self.clear_definitions_cache(name)
613
614    def add_nvalue(self, name, rule) -> None:
615        definition = self.get_user_definition(self.lookup_name(name))
616        definition.add_rule_at(rule, "n")
617        self.mark_changed(definition)
618        self.clear_definitions_cache(name)
619
620    def add_default(self, name, rule) -> None:
621        definition = self.get_user_definition(self.lookup_name(name))
622        definition.add_rule_at(rule, "default")
623        self.mark_changed(definition)
624        self.clear_definitions_cache(name)
625
626    def add_message(self, name, rule) -> None:
627        definition = self.get_user_definition(self.lookup_name(name))
628        definition.add_rule_at(rule, "messages")
629        self.mark_changed(definition)
630        self.clear_definitions_cache(name)
631
632    def set_values(self, name, values, rules) -> None:
633        pos = valuesname(values)
634        definition = self.get_user_definition(self.lookup_name(name))
635        definition.set_values_list(pos, rules)
636        self.mark_changed(definition)
637        self.clear_definitions_cache(name)
638
639    def get_options(self, name):
640        return self.get_definition(self.lookup_name(name)).options
641
642    def reset_user_definitions(self) -> None:
643        self.user = {}
644        self.clear_cache()
645        # TODO changed
646
647    def get_user_definitions(self):
648        return base64.encodebytes(pickle.dumps(self.user, protocol=2)).decode("ascii")
649
650    def set_user_definitions(self, definitions) -> None:
651        if definitions:
652            self.user = pickle.loads(base64.decodebytes(definitions.encode("ascii")))
653        else:
654            self.user = {}
655        self.clear_cache()
656
657    def get_ownvalue(self, name):
658        ownvalues = self.get_definition(self.lookup_name(name)).ownvalues
659        if ownvalues:
660            return ownvalues[0]
661        return None
662
663    def set_ownvalue(self, name, value) -> None:
664        from .expression import Symbol
665        from .rules import Rule
666
667        name = self.lookup_name(name)
668        self.add_rule(name, Rule(Symbol(name), value))
669        self.clear_cache(name)
670
671    def set_options(self, name, options) -> None:
672        definition = self.get_user_definition(self.lookup_name(name))
673        definition.options = options
674        self.mark_changed(definition)
675        self.clear_definitions_cache(name)
676
677    def unset(self, name, expr):
678        definition = self.get_user_definition(self.lookup_name(name))
679        result = definition.remove_rule(expr)
680        self.mark_changed(definition)
681        self.clear_definitions_cache(name)
682        return result
683
684    def get_config_value(self, name, default=None):
685        "Infinity -> None, otherwise returns integer."
686        value = self.get_definition(name).ownvalues
687        if value:
688            try:
689                value = value[0].replace
690            except AttributeError:
691                return None
692            if value.get_name() == "System`Infinity" or value.has_form(
693                "DirectedInfinity", 1
694            ):
695                return None
696
697            return int(value.get_int_value())
698        else:
699            return default
700
701    def set_config_value(self, name, new_value) -> None:
702        from mathics.core.expression import Integer
703
704        self.set_ownvalue(name, Integer(new_value))
705
706    def set_line_no(self, line_no) -> None:
707        self.set_config_value("$Line", line_no)
708
709    def get_line_no(self):
710        return self.get_config_value("$Line", 0)
711
712    def increment_line_no(self, increment: int = 1) -> None:
713        self.set_config_value("$Line", self.get_line_no() + increment)
714
715    def get_history_length(self):
716        history_length = self.get_config_value("$HistoryLength", 100)
717        if history_length is None or history_length > 100:
718            history_length = 100
719        return history_length
720
721
722def get_tag_position(pattern, name) -> typing.Optional[str]:
723    if pattern.get_name() == name:
724        return "own"
725    elif pattern.is_atom():
726        return None
727    else:
728        head_name = pattern.get_head_name()
729        if head_name == name:
730            return "down"
731        elif head_name == "System`Condition" and len(pattern.leaves) > 0:
732            return get_tag_position(pattern.leaves[0], name)
733        elif pattern.get_lookup_name() == name:
734            return "sub"
735        else:
736            for leaf in pattern.leaves:
737                if leaf.get_lookup_name() == name:
738                    return "up"
739        return None
740
741
742def insert_rule(values, rule) -> None:
743    for index, existing in enumerate(values):
744        if existing.pattern.sameQ(rule.pattern):
745            del values[index]
746            break
747    # use insort_left to guarantee that if equal rules exist, newer rules will
748    # get higher precedence by being inserted before them. see DownValues[].
749    bisect.insort_left(values, rule)
750
751
752class Definition(object):
753    def __init__(
754        self,
755        name,
756        rules=None,
757        ownvalues=None,
758        downvalues=None,
759        subvalues=None,
760        upvalues=None,
761        formatvalues=None,
762        messages=None,
763        attributes=(),
764        options=None,
765        nvalues=None,
766        defaultvalues=None,
767        builtin=None,
768    ) -> None:
769
770        super(Definition, self).__init__()
771        self.name = name
772
773        if rules is None:
774            rules = []
775        if ownvalues is None:
776            ownvalues = []
777        if downvalues is None:
778            downvalues = []
779        if subvalues is None:
780            subvalues = []
781        if upvalues is None:
782            upvalues = []
783        if formatvalues is None:
784            formatvalues = {}
785        if options is None:
786            options = {}
787        if nvalues is None:
788            nvalues = []
789        if defaultvalues is None:
790            defaultvalues = []
791        if messages is None:
792            messages = []
793
794        self.ownvalues = ownvalues
795        self.downvalues = downvalues
796        self.subvalues = subvalues
797        self.upvalues = upvalues
798        for rule in rules:
799            self.add_rule(rule)
800        self.formatvalues = dict((name, list) for name, list in formatvalues.items())
801        self.messages = messages
802        self.attributes = set(attributes)
803        for a in self.attributes:
804            assert "`" in a, "%s attribute %s has no context" % (name, a)
805        self.options = options
806        self.nvalues = nvalues
807        self.defaultvalues = defaultvalues
808        self.builtin = builtin
809
810    def get_values_list(self, pos):
811        assert pos.isalpha()
812        if pos == "messages":
813            return self.messages
814        else:
815            return getattr(self, "%svalues" % pos)
816
817    def set_values_list(self, pos, rules) -> None:
818        assert pos.isalpha()
819        if pos == "messages":
820            self.messages = rules
821        else:
822            setattr(self, "%svalues" % pos, rules)
823
824    def add_rule_at(self, rule, position) -> bool:
825        values = self.get_values_list(position)
826        insert_rule(values, rule)
827        return True
828
829    def add_rule(self, rule) -> bool:
830        pos = get_tag_position(rule.pattern, self.name)
831        if pos:
832            return self.add_rule_at(rule, pos)
833        return False
834
835    def remove_rule(self, lhs) -> bool:
836        position = get_tag_position(lhs, self.name)
837        if position:
838            values = self.get_values_list(position)
839            for index, existing in enumerate(values):
840                if existing.pattern.expr.sameQ(lhs):
841                    del values[index]
842                    return True
843        return False
844
845    def __repr__(self) -> str:
846        s = "<Definition: name: {}, downvalues: {}, formats: {}, attributes: {}>".format(
847            self.name, self.downvalues, self.formatvalues, self.attributes
848        )
849        return s
850