From 5c2ef0796f3b20e4d95bbf81b9c774be642b306a Mon Sep 17 00:00:00 2001 From: thatsOven <68780017+thatsOven@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:41:26 +0200 Subject: [PATCH 1/2] add case insensitive symbol lookup adds feature described in issue #9 --- pylasu/model/naming.py | 35 +++++++++++++++++++++++++++++++---- tests/model/test_model.py | 9 ++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/pylasu/model/naming.py b/pylasu/model/naming.py index 413e40c..0df01c0 100644 --- a/pylasu/model/naming.py +++ b/pylasu/model/naming.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from itertools import chain from typing import TypeVar, Generic, Optional, List, Dict @@ -33,14 +34,14 @@ def __hash__(self): def resolved(self): return self.referred is not None - def resolve(self, scope: 'Scope') -> bool: - self.referred = scope.lookup(symbol_name=self.name) + def resolve(self, scope: 'Scope', case_insensitive: bool = False) -> bool: + self.referred = scope.lookup(symbol_name=self.name, case_insensitive=case_insensitive) return self.resolved() def try_to_resolve(self, candidates: List[T], case_insensitive: bool = False) -> bool: """ Try to resolve the reference by finding a named element with a matching name. - The name match is performed in a case sensitive or insensitive way depending on the value of caseInsensitive. + The name match is performed in a case sensitive or insensitive way depending on the value of case_insensitive. """ def check_name(candidate: T) -> bool: @@ -60,10 +61,36 @@ class Symbol(PossiblyNamed): class Scope: symbols: Dict[str, List[Symbol]] = field(default_factory=list) parent: Optional['Scope'] = field(default=None) + insensitive_map: Optional[Dict[str, List[str]]] = field(default=None) + + def lookup(self, symbol_name: str, symbol_type: type = Symbol, case_insensitive: bool = False) -> Optional[Symbol]: + if case_insensitive: + if self.insensitive_map is None: + self.insensitive_map = {} + for key in self.symbols.keys(): + key_lower: str = key.lower() + self.insensitive_map[key_lower] = self.insensitive_map.get(key_lower, []) + [key] + + symbol_name_lower: str = symbol_name.lower() + + return next( + ( + symbol + for symbol in chain.from_iterable( + self.symbols.get(orig_symbol_name, []) + for orig_symbol_name in self.insensitive_map[symbol_name_lower] + ) + if isinstance(symbol, symbol_type) + ), + self.parent.lookup(symbol_name, symbol_type) if self.parent is not None else None + ) - def lookup(self, symbol_name: str, symbol_type: type = Symbol) -> Optional[Symbol]: return next((symbol for symbol in self.symbols.get(symbol_name, []) if isinstance(symbol, symbol_type)), self.parent.lookup(symbol_name, symbol_type) if self.parent is not None else None) def add(self, symbol: Symbol): self.symbols[symbol.name] = self.symbols.get(symbol.name, []) + [symbol] + + if self.insensitive_map is not None: + symbol_name_lower: str = symbol.name.lower() + self.insensitive_map[symbol_name_lower] = self.insensitive_map.get(symbol_name_lower, []) + [symbol.name] diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 542071b..2303b1d 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -136,6 +136,13 @@ def test_scope_lookup_7(self): result = scope.lookup(symbol_name='a', symbol_type=AnotherSymbol) self.assertIsNone(result) + def test_scope_case_insensitive_lookup(self): + local_symbol = SomeSymbol(name='a', index=0) + scope = Scope(symbols={'a': [local_symbol]}, parent=Scope(symbols={'a': [SomeSymbol(name='a', index=1)]})) + result = scope.lookup(symbol_name='A', case_insensitive=True) + self.assertEqual(result, local_symbol) + self.assertIsInstance(result, Symbol) + def test_node_properties_meta(self): pds = [pd for pd in sorted(SomeNode.node_properties, key=lambda x: x.name)] self.assertEqual(5, len(pds)) @@ -149,4 +156,4 @@ def test_node_properties_meta(self): self.assertEqual("name", pds[3].name) self.assertFalse(pds[3].provides_nodes) self.assertEqual("ref", pds[4].name) - self.assertTrue(pds[4].provides_nodes) + self.assertTrue(pds[4].provides_nodes) \ No newline at end of file From e03b1028429e7d76e2dd148368b3e82d51c0aaa5 Mon Sep 17 00:00:00 2001 From: thatsOven <68780017+thatsOven@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:49:40 +0200 Subject: [PATCH 2/2] modify @internal_properties behavior they are no longer overwritten in subclasses, rather they add onto the already present ones (inherited from the relative superclass) --- pylasu/model/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylasu/model/model.py b/pylasu/model/model.py index ce7cb90..561d3f3 100644 --- a/pylasu/model/model.py +++ b/pylasu/model/model.py @@ -14,7 +14,7 @@ class internal_property(property): def internal_properties(*props: str): def decorate(cls: type): - cls.__internal_properties__ = [*Node.__internal_properties__, *props] + cls.__internal_properties__ = getattr(cls, "__internal_properties__", []) + [*Node.__internal_properties__, *props] return cls return decorate