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