Castle: The best Real-Time/Embedded/HighTech language EVER. Attempt 2
Revision | c12c2e97c6f8e762bde8da07a08c4c8f9acbfb9d (tree) |
---|---|
Zeit | 2024-02-19 06:43:03 |
Autor | Albert Mietus < albert AT mietus DOT nl > |
Commiter | Albert Mietus < albert AT mietus DOT nl > |
moved aigr into base_packages/castle-aigr (code & test) TODO: Makefile, setup, ...
@@ -21,3 +21,4 @@ | ||
21 | 21 | html |
22 | 22 | Icon |
23 | 23 | DocParts/Design/*/*.png |
24 | +_setup.py | |
\ No newline at end of file |
@@ -0,0 +1,5 @@ | ||
1 | +CCastle2/castle/aigr/_ReadMe.rst | |
2 | +================================ | |
3 | + | |
4 | + | |
5 | + |
@@ -0,0 +1,24 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +from __future__ import annotations | |
4 | +from dataclasses import dataclass | |
5 | + | |
6 | + | |
7 | + | |
8 | +class AIGR: # Abstract Intermediate Graph Representation | |
9 | + def __new__(cls, *args, **kwargs): | |
10 | + if cls == AIGR: | |
11 | + raise NotImplementedError(f"Instantiate a subclass of {cls}, not the `Abstract Intermediate Graph Representation`` itself") | |
12 | + return super().__new__(cls) | |
13 | + | |
14 | + | |
15 | + | |
16 | +@dataclass | |
17 | +class _Marker: | |
18 | + msg :str="" | |
19 | + | |
20 | +from .events import * | |
21 | +from .protocols import * | |
22 | +from .interfaces import * | |
23 | +from .namespaces import * | |
24 | +from .namednodes import * |
@@ -0,0 +1,24 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | + | |
4 | +import typing as PTH # Python TypeHints | |
5 | +from enum import Enum | |
6 | +from dataclasses import dataclass, KW_ONLY | |
7 | +from . import AIGR | |
8 | + | |
9 | +# XXX__all__ = [ | |
10 | + | |
11 | +@dataclass | |
12 | +class TypedParameter(AIGR): | |
13 | + """This is many a helper class/struct to combine a parameter: a name and an type""" | |
14 | + name: str | |
15 | + type: type | |
16 | + | |
17 | +@dataclass | |
18 | +class Argument(AIGR): | |
19 | + """This is many a helper class/struct to combine a argument: a value and optional a name""" | |
20 | + value: PTH.Any | |
21 | + _: KW_ONLY | |
22 | + name: PTH.Optional[str]=None | |
23 | + | |
24 | + |
@@ -0,0 +1,22 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | + | |
4 | +import typing as PTH # Python TypeHints | |
5 | +from dataclasses import dataclass, KW_ONLY | |
6 | +from .aid import TypedParameter # Castle/AIGR type | |
7 | +from .namednodes import * | |
8 | + | |
9 | +__all__ = ['Event'] | |
10 | + | |
11 | + | |
12 | + | |
13 | + | |
14 | +@dataclass # pragma: no mutate | |
15 | +class Event(NamedNode): | |
16 | + """An event is like a (remote) function-call | |
17 | + | |
18 | + It has a name, a return-type (can be void), and an (inmutable) sequence of typed parameters.""" | |
19 | + _: KW_ONLY | |
20 | + return_type: PTH.Optional[type]=None | |
21 | + typedParameters: PTH.Sequence[TypedParameter]=() | |
22 | + |
@@ -0,0 +1,54 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | +from __future__ import annotations | |
3 | +import typing as PTH # Python TypeHints | |
4 | +from enum import Enum | |
5 | +from dataclasses import dataclass, KW_ONLY | |
6 | +from dataclasses import field as dc_field | |
7 | +from . import AIGR | |
8 | +from .protocols import Protocol | |
9 | +from .aid import TypedParameter # Castle/AIGR types | |
10 | +from .namednodes import NamedNode, ID | |
11 | + | |
12 | +__all__ = ['PortDirection', 'Port', 'ComponentInterface'] | |
13 | + | |
14 | +class PortDirection(Enum): | |
15 | + """Ports always have a direction; most port are ``Out`` (sending) or ``In``(receiving)``. | |
16 | + | |
17 | + More options are (will become) possible, like master/slave and bidir (=bidirectional)""" | |
18 | + | |
19 | + Unknown = 0 | |
20 | + In = 1 | |
21 | + Out = 2 | |
22 | + Bidir = 3 # Not supported yet | |
23 | + Bidirectional = Bidir | |
24 | + Master = 4 # Not supported yet | |
25 | + Slave = 5 # Not supported yet | |
26 | + | |
27 | +PortType = PTH.Union[Protocol, type] | |
28 | + | |
29 | +@dataclass | |
30 | +class Port(AIGR): # Note: not a NamedNode, as it does not live in a NS (but in a Component) | |
31 | + """.. note :: | |
32 | + | |
33 | + * ``Port``s do *not* inherit | |
34 | + * A `Port` has a type, like Event -- basically a protocol | |
35 | +""" | |
36 | + name: str | |
37 | + _: KW_ONLY | |
38 | + direction: PortDirection | |
39 | + type: PortType | |
40 | + | |
41 | + | |
42 | +@dataclass | |
43 | +class ComponentInterface(NamedNode): | |
44 | + _: KW_ONLY | |
45 | + based_on: PTH.Optional[ComponentInterface]=dc_field(default_factory= lambda: baseComponent) #type: ignore[has-type] | |
46 | + ports: PTH.Sequence[Port]=() | |
47 | + | |
48 | + def _noPorts(self): # left from (gone) auto-numbering - possible usefill as protocols has it to | |
49 | + inherited = self.based_on._noPorts() if isinstance(self.based_on, ComponentInterface) else 0 | |
50 | + return inherited + len(self.ports) | |
51 | + | |
52 | +_rootComponent=ComponentInterface(ID("RootComponent"), based_on=None, ports=()) # The base of the baseComponent | |
53 | +baseComponent=ComponentInterface(ID("Component"), based_on=_rootComponent, ports=()) #XXX Add base-ports | |
54 | + |
@@ -0,0 +1,36 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +from __future__ import annotations | |
4 | + | |
5 | +__all__ = ['NameError', 'NamedNode', 'ID'] | |
6 | + | |
7 | +from dataclasses import dataclass, KW_ONLY | |
8 | +from dataclasses import field as dc_field | |
9 | +import typing as PTH # Python TypeHints | |
10 | + | |
11 | +from . import AIGR | |
12 | + | |
13 | + | |
14 | +class NameError(AttributeError):pass | |
15 | + | |
16 | +class ID(str): pass #XXX for now and ID is a string, but that can be changed | |
17 | + | |
18 | + | |
19 | +@dataclass | |
20 | +class NamedNode(AIGR): | |
21 | + name :ID | |
22 | + _: KW_ONLY | |
23 | + # type(_ns) is NamedNode, but that leads to a cycle in imports, to use te more generic AIGR | |
24 | + _ns :PTH.Optional[AIGR]=dc_field(init=None, default=None) #type: ignore[call-overload] | |
25 | + | |
26 | + def __post_init__(self): | |
27 | + if not isinstance(self.name, ID): | |
28 | + self.name=ID(self.name) | |
29 | + | |
30 | + def register_in_NS(self, ns: AIGR): #same: type(ns) is NameSpace, but ... | |
31 | + self._ns = ns | |
32 | + | |
33 | + @property | |
34 | + def ns(self): | |
35 | + return self._ns | |
36 | + |
@@ -0,0 +1,93 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +"""This file contains AIGR-classes to model (all kind of) NameSpaces. | |
4 | + | |
5 | +There are several NameSpaces: the most prominent one is the ``Source_NS``, roughly the file that contains the (Castle) code. | |
6 | +""" | |
7 | +from __future__ import annotations | |
8 | + | |
9 | +__all__ = ['NameSpace', 'Source_NS'] | |
10 | + | |
11 | +import logging; logger = logging.getLogger(__name__) | |
12 | +import typing as PTH # Python TypeHints | |
13 | +from enum import Enum | |
14 | +from dataclasses import dataclass, KW_ONLY | |
15 | +from dataclasses import field as dc_field | |
16 | + | |
17 | +from .namednodes import NamedNode, NameError, ID | |
18 | + | |
19 | +from . import AIGR, _Marker | |
20 | + | |
21 | + | |
22 | +@dataclass | |
23 | +class NameSpace(NamedNode): | |
24 | + """This models a namespace (like a file, see ``Source_NS``). | |
25 | + | |
26 | + It contained *"named nodes"* that should be :method:`register()`ed and can be found by :method:`getID()` and/or :method:`findNode()`. | |
27 | + | |
28 | + XXX More""" | |
29 | + | |
30 | + _: KW_ONLY | |
31 | + _dict :PTH.Dict[ID, NamedNode]=dc_field(init=None, default_factory=lambda: dict()) #type: ignore[call-overload] | |
32 | + | |
33 | + def register(self, named_node :NamedNode, asName:PTH.Optional[ID|str]|str=None): | |
34 | + name = named_node.name if asName is None else ID(asName) | |
35 | + if name in self._dict: | |
36 | + old=self._dict[name] | |
37 | + logger.warning(f"The '{name}'-node is already in this namespace; -- it will be lost." + | |
38 | + f"Removed: {old}. New: {named_node}") | |
39 | + self._dict[name] = named_node | |
40 | + self._register_2ways(named_node) | |
41 | + | |
42 | + def _register_2ways(self, node): | |
43 | + node.register_in_NS(self) | |
44 | +### | |
45 | +### The following 3 methods are overkill. | |
46 | +### + findNode/getID only lock locally returning None (findNode) or raise NameError on no match | |
47 | +### + search is like findNode, but looks also in subNS'ses | |
48 | +### | |
49 | +### So, | |
50 | +### - ``NS.findNode(name)`` and ``NS.search(name)`` are equivalent | |
51 | +### (but search calls findNode, and can't be removed. find is also a better name) | |
52 | +###- There is no getID() for dottedName's | |
53 | +### | |
54 | + | |
55 | + | |
56 | + def findNode(self, name :ID|str) ->PTH.Optional[NamedNode]: | |
57 | + """Return the NamedNode with the specified name (aka ID), or None. | |
58 | + See :method:`getID` for an alternative""" | |
59 | + if not isinstance(name, ID): name=ID(name) | |
60 | + return self._dict.get(name, None) | |
61 | + | |
62 | + | |
63 | + def getID(self, name :ID) ->NamedNode: #Or raise NameError | |
64 | + """Return the NamedNode with the specified name (aka ID), or raised an NameError:AttributeError. | |
65 | + See :method:`findNode` for an alternative""" | |
66 | + node = self.findNode(name) | |
67 | + if node is None: | |
68 | + raise NameError(f"No node named {name} in NS:{self.name}") | |
69 | + return node | |
70 | + | |
71 | + def search(self, dottedName :ID) ->PTH.Optional[NamedNode]: | |
72 | + """Search the namespace for the 1st part of `dottedName`, then that NS for the next part, etc. And return the "deepest" node, or None""" | |
73 | + | |
74 | + parts = dottedName.split('.',maxsplit=1) # parts is [<name>, (<name>.)*] parts[1] can be absent, parts[0] always exist | |
75 | + node = self.findNode(parts[0]) | |
76 | + if len(parts) == 1: | |
77 | + return node | |
78 | + try: | |
79 | + return node.search(parts[1]) #type: ignore[union-attr] # Assume a NS, else raise | |
80 | + except AttributeError: #node isn't a search'able/NameSpace --> Not found --> return None | |
81 | + return None | |
82 | + | |
83 | + def find_byType(self, cls:type) ->dict[ID, NamedNode]: | |
84 | + return {name: node for name, node in self._dict.items() if isinstance(node, cls)} | |
85 | + | |
86 | + def all_NS(self) ->dict[ID, NamedNode]: | |
87 | + return self.find_byType(NameSpace) | |
88 | + | |
89 | + | |
90 | +@dataclass | |
91 | +class Source_NS(NameSpace): | |
92 | + _: KW_ONLY | |
93 | + source :PTH.Optional[str]=None |
@@ -0,0 +1,100 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +""" See ./ReadMe.rst""" | |
4 | + | |
5 | +from __future__ import annotations | |
6 | +import typing as PTH # Python TypeHints | |
7 | +from enum import Enum | |
8 | +from dataclasses import dataclass, KW_ONLY | |
9 | +from dataclasses import field as dc_field | |
10 | +from . import AIGR | |
11 | +from .events import Event | |
12 | +from .aid import TypedParameter, Argument # Castle/AIGR types | |
13 | +from .namednodes import NamedNode, ID | |
14 | + | |
15 | + | |
16 | + | |
17 | +__all__ = ['ProtocolKind', 'Protocol', 'EventProtocol'] | |
18 | +# DataProtocol, StreamProtocol are added/implemented later | |
19 | +# Do Not Export: _RootProtocol | |
20 | +# How about: ProtocolWrapper? | |
21 | + | |
22 | +class ProtocolKind(Enum): | |
23 | + """There are several kinds (types) of protocols. | |
24 | + | |
25 | + This can be modeled by subclassing (in langueas that support it), or | |
26 | + by using a low-int (aka a enum) and save that in the struct""" | |
27 | + Unknown = 0 | |
28 | + Event = 1 | |
29 | + Data = 2 | |
30 | + Stream = 3 | |
31 | + _unset = -1 | |
32 | + | |
33 | +@dataclass | |
34 | +class Protocol(NamedNode): | |
35 | + """ .. note:: Use one of the subclasses -- Only Event is defined yet | |
36 | + .. todo:: Design: What is the `kind` self and the inherited ones are not the same? | |
37 | + overriding ProtocolKind.Unknown is always allowed | |
38 | + """ | |
39 | + _BASE: PTH.ClassVar=None # pragma: no mutate | |
40 | + | |
41 | + _: KW_ONLY | |
42 | + kind :ProtocolKind | |
43 | + based_on :PTH.Optional[Protocol]=dc_field(default_factory= lambda :Protocol._BASE) # pragma: no mutate | |
44 | + typedParameters :PTH.Optional[PTH.Sequence[TypedParameter]]=() | |
45 | + | |
46 | + | |
47 | +@dataclass # pragma: no mutate | |
48 | +class _RootProtocol(Protocol): | |
49 | + """This is the base protocol; it exist as we can't instantiate Protocol""" | |
50 | + | |
51 | +baseProtocol = _RootProtocol(ID("Protocol"), kind=ProtocolKind.Unknown, based_on=None) # pragma: no mutate | |
52 | +Protocol._BASE=baseProtocol | |
53 | + | |
54 | +@dataclass # pragma: no mutate | |
55 | +class DataProtocol(Protocol): pass ### XXX ToDo (not exported) | |
56 | +@dataclass # pragma: no mutate | |
57 | +class StreamProtocol(Protocol): pass ### XXX ToDo (not exported) | |
58 | + | |
59 | + | |
60 | +@dataclass # pragma: no mutate | |
61 | +class ProtocolWrapper(Protocol): | |
62 | + _: KW_ONLY | |
63 | + kind : ProtocolKind=ProtocolKind._unset | |
64 | + arguments: PTH.Sequence[Argument] | |
65 | + | |
66 | + def __post_init__(self): | |
67 | + if self.kind is ProtocolKind._unset: | |
68 | + self.kind = self.based_on.kind | |
69 | + if self.name == "": | |
70 | + self.name = f"Wrapper for {self.based_on.name}({self.arguments})" ### | |
71 | + | |
72 | + def __getattr__(self, name): #delegate to base XXX move to Wrapper Base? | |
73 | + return getattr(self.based_on, name) | |
74 | + | |
75 | + | |
76 | +@dataclass # pragma: no mutate | |
77 | +class EventProtocol(Protocol): | |
78 | + """An event-based protocol is basically a set of events. | |
79 | + | |
80 | + This recorded as an dyn-array of the new event; there is no need to copy the inherited ones | |
81 | + """ | |
82 | + _: KW_ONLY | |
83 | + kind: ProtocolKind = ProtocolKind.Event | |
84 | + events: PTH.Sequence[Event] | |
85 | + #redefine EventProtocol always inherit from an EventProtocol (or Wrapper) | |
86 | + based_on: EventProtocol|ProtocolWrapper = dc_field(default_factory= lambda :Protocol._BASE) | |
87 | + | |
88 | + def _noEvents(self): | |
89 | + inherited = self.based_on._noEvents() if isinstance(self.based_on, EventProtocol) else 0 | |
90 | + return inherited + len(self.events) | |
91 | + | |
92 | + def eventIndex(self, event: Event) -> int: | |
93 | + try: | |
94 | + return self.based_on.eventIndex(event) | |
95 | + except ValueError: # not inherited | |
96 | + inherited_events = self.based_on._noEvents() | |
97 | + except AttributeError: # self.based_on === None | |
98 | + inherited_events = 0 | |
99 | + # `event` not inherited: search locally and number of inherited events | |
100 | + return self.events.index(event) + inherited_events |
@@ -0,0 +1,28 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +import pytest | |
4 | + | |
5 | +from castle.aigr import AIGR | |
6 | + | |
7 | +class Demo(AIGR): | |
8 | + pass | |
9 | + | |
10 | +def test_noAIGR(): | |
11 | + try: | |
12 | + AIGR() | |
13 | + assert False , "shouldn't be able to initiate an AIGR directly" # pragma: no cover | |
14 | + except NotImplementedError: | |
15 | + pass | |
16 | + | |
17 | +def test_AIGR_sub(): | |
18 | + d = Demo() | |
19 | + assert isinstance(d, AIGR) | |
20 | + | |
21 | +@pytest.mark.skip("The ID comes A bit later") | |
22 | +def test_ID_many(): | |
23 | + assert False, """Make an ID class in AIGR (or generic). ((also see NamedNode)) | |
24 | + | |
25 | + It holds the name in AIGR -nodes ipv de str class. Model it a bit like pathlib.Path | |
26 | + | |
27 | + We should have atomic_ID, relative_ID, DottedName_ID, ect | |
28 | + """ |
@@ -0,0 +1,12 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +import logging; logger = logging.getLogger(__name__) | |
4 | +import pytest | |
5 | + | |
6 | +from castle.aigr import aid | |
7 | + | |
8 | +def test_Argument_with_NoName(): | |
9 | + a = aid.Argument(value=1) | |
10 | + | |
11 | + assert a.name == None | |
12 | + assert a.value == 1 |
@@ -0,0 +1,22 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +from castle.aigr import Event | |
4 | +from castle.aigr.aid import TypedParameter | |
5 | + | |
6 | + | |
7 | +def test_0_Event_empty(): | |
8 | + e = Event("leeg_event") | |
9 | + assert e.name == 'leeg_event' | |
10 | + assert e.return_type is None | |
11 | + assert len(e.typedParameters) == 0 | |
12 | + | |
13 | +def test_1_Event_small(): | |
14 | + e = Event("demo_int", typedParameters=[TypedParameter(name='p1', type=float )]) | |
15 | + assert e.return_type is None | |
16 | + assert len(e.typedParameters) == 1 | |
17 | + assert e.typedParameters[0].name == 'p1' | |
18 | + assert e.typedParameters[0].type == float | |
19 | + | |
20 | +def test_2_Event_retunInt(): | |
21 | + e = Event("an_event", return_type=int) | |
22 | + assert e.return_type == int |
@@ -0,0 +1,24 @@ | ||
1 | +# (C) Albert Mietus, 2022, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +from castle.aigr import ProtocolKind | |
4 | + | |
5 | + | |
6 | +def test_0_Unknow_value(): | |
7 | + assert ProtocolKind.Unknown.value ==0 | |
8 | + | |
9 | +def test_O_unset_value(): | |
10 | + assert ProtocolKind._unset.value < 0 | |
11 | + | |
12 | +def test_1_NameExist(): | |
13 | + assert ProtocolKind.Unknown | |
14 | + assert ProtocolKind.Event | |
15 | + assert ProtocolKind.Data | |
16 | + assert ProtocolKind.Stream | |
17 | + | |
18 | +def test_2_ConsecutiveSeries(): | |
19 | + values = sorted(k.value for k in ProtocolKind) | |
20 | + v1 = values[0] | |
21 | + for v2 in values[1:]: | |
22 | + assert v1+1==v2, f"{ProtocolKind(v1)}+1 <> {ProtocolKind(v2)}" | |
23 | + v1=v2 | |
24 | + |
@@ -0,0 +1,95 @@ | ||
1 | +# (C) Albert Mietus, 2022, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +import logging; logger = logging.getLogger(__name__) | |
4 | +import pytest | |
5 | + | |
6 | +from castle.aigr import Protocol, ProtocolKind | |
7 | +from castle.aigr import Event, EventProtocol | |
8 | +from castle.aigr.aid import TypedParameter | |
9 | + | |
10 | + | |
11 | +@pytest.fixture | |
12 | +def emptyProtocol(): | |
13 | + return EventProtocol("EMPTY", events=[], based_on=None) | |
14 | + | |
15 | +@pytest.fixture | |
16 | +def emptyProtocol_baseNotSet(): | |
17 | + return Protocol("VeryEmpty", kind=ProtocolKind.Unknown) | |
18 | + | |
19 | +@pytest.fixture | |
20 | +def anEvent(): | |
21 | + return Event("input", typedParameters=[TypedParameter(name='event', type=int)]) | |
22 | + | |
23 | +@pytest.fixture | |
24 | +def simpleSieve(anEvent): | |
25 | + return EventProtocol("SimpleSieve", events=[anEvent]) | |
26 | + | |
27 | + | |
28 | + | |
29 | +def test_1_isEvent(emptyProtocol, simpleSieve): | |
30 | + assert emptyProtocol.kind == ProtocolKind.Event | |
31 | + assert simpleSieve.kind == ProtocolKind.Event | |
32 | + | |
33 | + | |
34 | +def test_2a_based_onRoot(emptyProtocol): | |
35 | + import castle.aigr.protocols | |
36 | + emptyProtocol.based_on is castle.aigr.protocols._RootProtocol | |
37 | + | |
38 | +def test_2b_based_onRoot_notSet(emptyProtocol_baseNotSet): | |
39 | + import castle.aigr.protocols | |
40 | + emptyProtocol_baseNotSet.based_on is castle.aigr.protocols._RootProtocol | |
41 | + | |
42 | + | |
43 | +def test_3a_eventIndex_empty(emptyProtocol, anEvent): | |
44 | + assert emptyProtocol._noEvents() == 0 | |
45 | + try: | |
46 | + emptyProtocol.eventIndex(anEvent) | |
47 | + assert False, f"{anEvent} shouldn't be in the emptyProtocol" # pragma: no cover | |
48 | + except ValueError: pass | |
49 | + | |
50 | + | |
51 | +def test_3b_eventIndex_simple(simpleSieve, anEvent): | |
52 | + assert simpleSieve._noEvents() == 1 | |
53 | + assert simpleSieve.eventIndex(anEvent) == 0, f"`anEvent` should be eventIndex==0, but isn;t...T\n {anEvent}\n{simpleSieve.events}" | |
54 | + | |
55 | + | |
56 | +def test_3c_eventIndex_inherited(): | |
57 | + e0 = Event("E0") | |
58 | + e1 = Event("E1") | |
59 | + e2 = Event("E2") | |
60 | + p0 = EventProtocol("P0", events=[e0]) | |
61 | + p1 = EventProtocol("P1", events=[e1,e2], based_on=p0) | |
62 | + | |
63 | + assert p1._noEvents() == 3 | |
64 | + assert p1.eventIndex(e2) == 2 | |
65 | + | |
66 | + | |
67 | +def test_protocol_with_Noparms(emptyProtocol): | |
68 | + assert emptyProtocol.typedParameters == () | |
69 | + | |
70 | + | |
71 | +def test_protocol_with_aParm(): | |
72 | + e = EventProtocol("With_a_parm", events=[], based_on=None, | |
73 | + typedParameters=[TypedParameter(name='p', type=float)]) | |
74 | + assert len(e.typedParameters) ==1 | |
75 | + assert e.typedParameters[0].name == 'p' | |
76 | + assert e.typedParameters[0].type == float | |
77 | + | |
78 | + | |
79 | +def test_protocol_with_4Parms(): | |
80 | + e = EventProtocol("With_4_Parms", events=[], based_on=None, | |
81 | + typedParameters=( | |
82 | + TypedParameter(name='p0', type=float ), | |
83 | + TypedParameter(name='p1', type=int ), | |
84 | + TypedParameter(name='p2', type=str ), | |
85 | + TypedParameter(name='p3', type=None ), | |
86 | + )) | |
87 | + assert len(e.typedParameters) == 4 | |
88 | + assert (e.typedParameters[0].name, e.typedParameters[0].type) == ('p0', float) | |
89 | + assert (e.typedParameters[1].name, e.typedParameters[1].type) == ('p1', int) | |
90 | + assert (e.typedParameters[2].name, e.typedParameters[2].type) == ('p2', str) | |
91 | + assert (e.typedParameters[3].name, e.typedParameters[3].type) == ('p3', None) | |
92 | + | |
93 | + | |
94 | +#Note: for more complicated cases, see :file:`test_2c_WrappedProtocols.py` | |
95 | + |
@@ -0,0 +1,119 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +""" With :file:`test_2b_protocol.py` most/all normal cases are verified. This file focus on **GenericProtocols**: | |
4 | + GenericProtocols are protocols that inherited from a protocol with parameters. We focus on EventProtocols, but it | |
5 | + will be entende to all protocols. | |
6 | + | |
7 | + .. code-block: Castle: | |
8 | + | |
9 | + protocol SlowStart(queue_max:int): EventProtocol ... | |
10 | + protocol SimpleSieve : SlowStart(1) ... | |
11 | + | |
12 | + * `SlowStart` is a **Generic Protocol** with a (1) parameter: the (initial) (max) size of the queue. | |
13 | + * `SimpleSieve` used that protocol, and set that parameter to 1. | |
14 | + | |
15 | + The parameter to SlowStart, in SimpleSieve, can be seen as a template-specialisation as it is called in C++ | |
16 | + | |
17 | + .. code-block: C++ | |
18 | + | |
19 | + // A C++ Template approximation of above: using classes not protocols ... | |
20 | + template <int queue_max> | |
21 | + class SlowStart {...} | |
22 | + | |
23 | + class SimpleSieve : SlowStart<1> { ...} | |
24 | + | |
25 | + In both examples the value ``1`` is filled in (aka hard-coded) into the implementation of SlowStart: Wherever | |
26 | + `queue_max` is used the value `1` is used -- as if the source always has had a `1`.... | |
27 | + | |
28 | + .. note:: The Castle syntax uses parentheses, as for normal (formal) arguments, not chevrons <angle brackets> as in C++ | |
29 | + | |
30 | + It read as-if SlowStart is instantiated (as in Python: calling ``SlowStart()``), but actually it is | |
31 | + **specialised*, by filling in the template/parentheses. The result is a not an instance of SlowStart, but a "new | |
32 | + Protocol, which kind-of inherits from SlowStart -- and SimpleSieve inherits from that one. | |
33 | + | |
34 | + This *syntax detail* is handled in the parser! | |
35 | + | |
36 | + In the AIGR, the specialised *SlowStart(1)* protocol is modeled by a ProtocolWrapper; which in placed in-between | |
37 | + (the generic) Slowstart and SimpleSieve. | |
38 | + | |
39 | + .. hint:: (implementation of Generic Protocols) | |
40 | + | |
41 | + With ``functools.partial``, it seams possible to make a "partial class". BUT, that is NOT VALID RPython! | |
42 | + (see: .../PyPy+Rpython/new/partialCLass.py). | |
43 | + | |
44 | + So, it will be implemented in normal the normale AST-->AIGT-->AIGT-->RPY train (as in C++ template's): just fill in | |
45 | +""" | |
46 | + | |
47 | +import logging; logger = logging.getLogger(__name__) | |
48 | +import pytest | |
49 | + | |
50 | +from castle.aigr import Protocol, ProtocolKind | |
51 | +from castle.aigr import Event, EventProtocol | |
52 | +from castle.aigr.aid import TypedParameter, Argument | |
53 | +from castle.aigr.protocols import ProtocolWrapper | |
54 | + | |
55 | +""" There are a few cases | |
56 | +///CastleCode | |
57 | + protocol Base(queue_max:int): EventProtocol | |
58 | + protocol Sub_a: Base(queue_max=1) # (a): named argument | |
59 | + protocol Sub_b: Base(1) # (b): positional arg | |
60 | + | |
61 | +""" | |
62 | + | |
63 | + | |
64 | +@pytest.fixture | |
65 | +def base(): | |
66 | + return EventProtocol("Base", events=[], typedParameters=[TypedParameter(name='queue_max', type=int)]) | |
67 | + | |
68 | +@pytest.fixture | |
69 | +def sub_a(base): | |
70 | + return EventProtocol("Sub_a", events=[], based_on=ProtocolWrapper(name="", based_on=base, arguments=(Argument(name='queue_max', value=1),))) | |
71 | + | |
72 | +@pytest.fixture | |
73 | +def sub_b(base): | |
74 | + return EventProtocol("Sub_b", events=[], based_on=ProtocolWrapper("", based_on=base, arguments=(Argument(value=1),))) | |
75 | + | |
76 | +def assert_GP_kind(base, sub): | |
77 | + assert sub.kind == base.kind | |
78 | + assert sub.based_on.kind == base.kind | |
79 | + assert sub.based_on.based_on is base | |
80 | + | |
81 | +def test_GenericProtocol_kind_a(base, sub_a): | |
82 | + assert_GP_kind(base, sub_a) | |
83 | + | |
84 | +def test_GenericProtocol_kind_b(base, sub_b): | |
85 | + assert_GP_kind(base, sub_b) | |
86 | + | |
87 | + | |
88 | +def assert_GP_name(base, sub): | |
89 | + assert "Wrapper" in sub.based_on.name | |
90 | + assert "Base" in sub.based_on.name | |
91 | + | |
92 | +def test_GenericProtocol_name_a(base, sub_a): | |
93 | + assert_GP_name(base, sub_a) | |
94 | + assert "queue_max" in sub_a.based_on.name # the argument-name is only in the (a) version | |
95 | + | |
96 | +def test_GenericProtocol_name_b(base, sub_b): | |
97 | + assert_GP_name(base, sub_b) | |
98 | + assert "queue_max" not in sub_b.based_on.name # the argument-name is only in the (a) version | |
99 | + | |
100 | + | |
101 | + | |
102 | +def test_strange_1(base): | |
103 | + """This is (very) atypical use -- but it helps to get coverage""" | |
104 | + sub_strange = EventProtocol("SubStrange", events=[], based_on=ProtocolWrapper(name="Strange", | |
105 | + kind=42, | |
106 | + based_on=base, | |
107 | + arguments=(Argument(value=1),))) | |
108 | + assert sub_strange.based_on.name == "Strange" | |
109 | + assert sub_strange.based_on.kind == 42, "When we set a strange kind-number it should be stored" | |
110 | + | |
111 | +def test_strange_2(base): | |
112 | + """This is (very) atypical use -- but it helps to get coverage""" | |
113 | + sub_strange = EventProtocol("SubStrange", events=[], based_on=ProtocolWrapper(name="Strange", | |
114 | + kind=None, | |
115 | + based_on=base, | |
116 | + arguments=(Argument(value=1),))) | |
117 | + | |
118 | + assert sub_strange.based_on.name == "Strange" | |
119 | + |
@@ -0,0 +1,155 @@ | ||
1 | +# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | + | |
3 | +import logging; logger = logging.getLogger(__name__) | |
4 | +import pytest | |
5 | +import typing as PTH # Python TypeHints | |
6 | +from dataclasses import dataclass, KW_ONLY | |
7 | +from random import randint | |
8 | + | |
9 | +from castle.aigr import NameSpace, Source_NS | |
10 | + | |
11 | +from castle.aigr.namespaces import NamedNode ## May be moved ip | |
12 | +from castle.aigr.namespaces import NameError | |
13 | + | |
14 | +@dataclass | |
15 | +class DummyNode(NamedNode): | |
16 | + name :str | |
17 | + _: KW_ONLY | |
18 | + dummy :PTH.Any=None | |
19 | + | |
20 | +@pytest.fixture | |
21 | +def a_node(): | |
22 | + return DummyNode("a_node", dummy=randint(42,2023)) | |
23 | + | |
24 | +@pytest.fixture | |
25 | +def aNS(a_node): | |
26 | + ns = NameSpace("aNS") | |
27 | + ns.register(a_node) | |
28 | + return ns | |
29 | + | |
30 | +@pytest.fixture | |
31 | +def top(): | |
32 | + top = NameSpace('top') | |
33 | + return top | |
34 | + | |
35 | +@pytest.fixture | |
36 | +def sub(top): | |
37 | + sub = NameSpace('sub') | |
38 | + top.register(sub) | |
39 | + return sub | |
40 | + | |
41 | +@pytest.fixture | |
42 | +def sourceNS(a_node): | |
43 | + ns = Source_NS("sourceNS", source="dummy") | |
44 | + ns.register(a_node) | |
45 | + return ns | |
46 | + | |
47 | + | |
48 | +def test_1_NS_stored(a_node, aNS): | |
49 | + name = a_node.name | |
50 | + assert aNS.getID(name) is a_node | |
51 | + assert aNS.findNode(name) is a_node | |
52 | + | |
53 | + | |
54 | +def test_2_NS_find_vs_get_when_not_registered(aNS): | |
55 | + assert aNS.findNode("Deze Bestaat Niet") is None | |
56 | + try: | |
57 | + aNS.getID("Deze Bestaat Niet") | |
58 | + assert False, """`aNS.getID("Deze Bestaat Niet")` should raise an error""" | |
59 | + except NameError: pass | |
60 | + | |
61 | + | |
62 | +def test_3_sourceNS_combi(a_node, sourceNS): | |
63 | + "The functionality as shown in _NS1 & _NS2 should also work with Source_NS" | |
64 | + name = a_node.name | |
65 | + assert sourceNS.getID(name) is a_node | |
66 | + assert sourceNS.findNode(name) is a_node | |
67 | + | |
68 | + assert sourceNS.findNode("Deze Bestaat Niet") is None | |
69 | + try: | |
70 | + sourceNS.getID("Deze Bestaat Niet") | |
71 | + assert False, """`sourceNS.getID("Deze Bestaat Niet")` should raise an error""" | |
72 | + except NameError: pass | |
73 | + | |
74 | + | |
75 | +def test_4_sameName_is_replaced(aNS): | |
76 | + logger.warning("""NOTICE: This test will issue the warning 'astle.aigr.namespaces:namespaces.py:42' You should ignore it""") | |
77 | + name='TriggerWarning' | |
78 | + one = DummyNode(name, dummy='one') | |
79 | + two = DummyNode(name, dummy='one') | |
80 | + aNS.register(one); assert aNS.getID(name) is one #No test, just verify | |
81 | + | |
82 | + aNS.register(two) | |
83 | + assert aNS.getID(name) is two #The test | |
84 | + | |
85 | + | |
86 | +def test_5a_ns_in_ns(): | |
87 | + "when we import a NS, we get a NS in a NS ..." | |
88 | + top = NameSpace('top') | |
89 | + sub = NameSpace('sub') | |
90 | + elm = DummyNode('elm', dummy="with.dotted.Name") | |
91 | + top.register(sub) | |
92 | + sub.register(elm) | |
93 | + | |
94 | + assert top.getID('sub') is sub | |
95 | + assert sub.getID('elm') is elm | |
96 | + assert top.search(dottedName="sub.elm") is elm | |
97 | + | |
98 | + | |
99 | +def test_5b_seach_1level(aNS,a_node): | |
100 | + name = a_node.name | |
101 | + assert (aNS.search(name) is a_node) and (aNS.getID(name) is a_node), "search should find that what getID returns" | |
102 | + | |
103 | + | |
104 | +def test_5c_seachNotFound_1(top): | |
105 | + assert top.search("Deze bestaat niet") is None | |
106 | + | |
107 | +def test_5d_seachNotFound_sub(top, sub): | |
108 | + assert top.search("top.Deze.bestaat.niet") is None | |
109 | + | |
110 | +def test_6a_registered_is_2ways(aNS, a_node): | |
111 | + """When a NamedNode is registered in a NameSpace, it should a backlink (`ns property) to the NS again""" | |
112 | + assert a_node.ns is aNS | |
113 | + | |
114 | +def test_6b_registered_is_2ways_once(aNS, a_node): | |
115 | + """Currently, a NamedNode can be registered in multiple namespaces, but the backlink is always the last | |
116 | + XXX ToDo: is that the intent? For now test as is""" | |
117 | + name = a_node.name | |
118 | + other = NameSpace('other') | |
119 | + other.register(a_node) | |
120 | + | |
121 | + assert (aNS.getID(name) is a_node) and (other.getID(name) is a_node), "A NamedNode can be registered in two NS'ses ..." | |
122 | + assert a_node.ns is other, " ...but Only the last NS is remembered" | |
123 | + | |
124 | +def test_7_alias(aNS): | |
125 | + node=DummyNode("aliased") | |
126 | + alias="anOtherName" | |
127 | + aNS.register(node, asName=alias) | |
128 | + assert aNS.findNode(name=alias) is node, f"it should be registered with the given alias: {alias}" | |
129 | + assert aNS.findNode(name=node.name) is None, f"The realname should not be registered" | |
130 | + | |
131 | + | |
132 | +def test_byType_None(aNS): | |
133 | + d = aNS.find_byType(type(None)) # There should be None's in aNS | |
134 | + assert isinstance(d, dict) | |
135 | + assert len(d)==0 | |
136 | + | |
137 | +def test_byType_Dummy(aNS, a_node): | |
138 | + d = aNS.find_byType(DummyNode) | |
139 | + assert len(d)==1 | |
140 | + assert a_node.name in d | |
141 | + assert d[a_node.name] is a_node # note: this assumed no aliasses are used ('asName') | |
142 | + | |
143 | +def test_byType_NS(top, sub, sourceNS): | |
144 | + top.register(sourceNS) # Note: sub is already 'in; top | |
145 | + | |
146 | + d = top.find_byType(NameSpace) | |
147 | + assert len(d) == 2 # sub, sourceNS | |
148 | + assert d['sub'] is sub | |
149 | + assert d['sourceNS'] is sourceNS | |
150 | + | |
151 | + | |
152 | + | |
153 | +@pytest.mark.skip("Todo: Unite `.search()` and `.find()` [& `.getID()] -- see comment in `aigr/namespaces.py`") | |
154 | +def test_ToDo_Unite(): | |
155 | + assert False |
@@ -1,5 +0,0 @@ | ||
1 | -CCastle2/castle/aigr/_ReadMe.rst | |
2 | -================================ | |
3 | - | |
4 | - | |
5 | - |
@@ -1,24 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -from __future__ import annotations | |
4 | -from dataclasses import dataclass | |
5 | - | |
6 | - | |
7 | - | |
8 | -class AIGR: # Abstract Intermediate Graph Representation | |
9 | - def __new__(cls, *args, **kwargs): | |
10 | - if cls == AIGR: | |
11 | - raise NotImplementedError(f"Instantiate a subclass of {cls}, not the `Abstract Intermediate Graph Representation`` itself") | |
12 | - return super().__new__(cls) | |
13 | - | |
14 | - | |
15 | - | |
16 | -@dataclass | |
17 | -class _Marker: | |
18 | - msg :str="" | |
19 | - | |
20 | -from .events import * | |
21 | -from .protocols import * | |
22 | -from .interfaces import * | |
23 | -from .namespaces import * | |
24 | -from .namednodes import * |
@@ -1,24 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | - | |
4 | -import typing as PTH # Python TypeHints | |
5 | -from enum import Enum | |
6 | -from dataclasses import dataclass, KW_ONLY | |
7 | -from . import AIGR | |
8 | - | |
9 | -# XXX__all__ = [ | |
10 | - | |
11 | -@dataclass | |
12 | -class TypedParameter(AIGR): | |
13 | - """This is many a helper class/struct to combine a parameter: a name and an type""" | |
14 | - name: str | |
15 | - type: type | |
16 | - | |
17 | -@dataclass | |
18 | -class Argument(AIGR): | |
19 | - """This is many a helper class/struct to combine a argument: a value and optional a name""" | |
20 | - value: PTH.Any | |
21 | - _: KW_ONLY | |
22 | - name: PTH.Optional[str]=None | |
23 | - | |
24 | - |
@@ -1,22 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | - | |
4 | -import typing as PTH # Python TypeHints | |
5 | -from dataclasses import dataclass, KW_ONLY | |
6 | -from .aid import TypedParameter # Castle/AIGR type | |
7 | -from .namednodes import * | |
8 | - | |
9 | -__all__ = ['Event'] | |
10 | - | |
11 | - | |
12 | - | |
13 | - | |
14 | -@dataclass # pragma: no mutate | |
15 | -class Event(NamedNode): | |
16 | - """An event is like a (remote) function-call | |
17 | - | |
18 | - It has a name, a return-type (can be void), and an (inmutable) sequence of typed parameters.""" | |
19 | - _: KW_ONLY | |
20 | - return_type: PTH.Optional[type]=None | |
21 | - typedParameters: PTH.Sequence[TypedParameter]=() | |
22 | - |
@@ -1,54 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | -from __future__ import annotations | |
3 | -import typing as PTH # Python TypeHints | |
4 | -from enum import Enum | |
5 | -from dataclasses import dataclass, KW_ONLY | |
6 | -from dataclasses import field as dc_field | |
7 | -from . import AIGR | |
8 | -from .protocols import Protocol | |
9 | -from .aid import TypedParameter # Castle/AIGR types | |
10 | -from .namednodes import NamedNode, ID | |
11 | - | |
12 | -__all__ = ['PortDirection', 'Port', 'ComponentInterface'] | |
13 | - | |
14 | -class PortDirection(Enum): | |
15 | - """Ports always have a direction; most port are ``Out`` (sending) or ``In``(receiving)``. | |
16 | - | |
17 | - More options are (will become) possible, like master/slave and bidir (=bidirectional)""" | |
18 | - | |
19 | - Unknown = 0 | |
20 | - In = 1 | |
21 | - Out = 2 | |
22 | - Bidir = 3 # Not supported yet | |
23 | - Bidirectional = Bidir | |
24 | - Master = 4 # Not supported yet | |
25 | - Slave = 5 # Not supported yet | |
26 | - | |
27 | -PortType = PTH.Union[Protocol, type] | |
28 | - | |
29 | -@dataclass | |
30 | -class Port(AIGR): # Note: not a NamedNode, as it does not live in a NS (but in a Component) | |
31 | - """.. note :: | |
32 | - | |
33 | - * ``Port``s do *not* inherit | |
34 | - * A `Port` has a type, like Event -- basically a protocol | |
35 | -""" | |
36 | - name: str | |
37 | - _: KW_ONLY | |
38 | - direction: PortDirection | |
39 | - type: PortType | |
40 | - | |
41 | - | |
42 | -@dataclass | |
43 | -class ComponentInterface(NamedNode): | |
44 | - _: KW_ONLY | |
45 | - based_on: PTH.Optional[ComponentInterface]=dc_field(default_factory= lambda: baseComponent) #type: ignore[has-type] | |
46 | - ports: PTH.Sequence[Port]=() | |
47 | - | |
48 | - def _noPorts(self): # left from (gone) auto-numbering - possible usefill as protocols has it to | |
49 | - inherited = self.based_on._noPorts() if isinstance(self.based_on, ComponentInterface) else 0 | |
50 | - return inherited + len(self.ports) | |
51 | - | |
52 | -_rootComponent=ComponentInterface(ID("RootComponent"), based_on=None, ports=()) # The base of the baseComponent | |
53 | -baseComponent=ComponentInterface(ID("Component"), based_on=_rootComponent, ports=()) #XXX Add base-ports | |
54 | - |
@@ -1,36 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -from __future__ import annotations | |
4 | - | |
5 | -__all__ = ['NameError', 'NamedNode', 'ID'] | |
6 | - | |
7 | -from dataclasses import dataclass, KW_ONLY | |
8 | -from dataclasses import field as dc_field | |
9 | -import typing as PTH # Python TypeHints | |
10 | - | |
11 | -from . import AIGR | |
12 | - | |
13 | - | |
14 | -class NameError(AttributeError):pass | |
15 | - | |
16 | -class ID(str): pass #XXX for now and ID is a string, but that can be changed | |
17 | - | |
18 | - | |
19 | -@dataclass | |
20 | -class NamedNode(AIGR): | |
21 | - name :ID | |
22 | - _: KW_ONLY | |
23 | - # type(_ns) is NamedNode, but that leads to a cycle in imports, to use te more generic AIGR | |
24 | - _ns :PTH.Optional[AIGR]=dc_field(init=None, default=None) #type: ignore[call-overload] | |
25 | - | |
26 | - def __post_init__(self): | |
27 | - if not isinstance(self.name, ID): | |
28 | - self.name=ID(self.name) | |
29 | - | |
30 | - def register_in_NS(self, ns: AIGR): #same: type(ns) is NameSpace, but ... | |
31 | - self._ns = ns | |
32 | - | |
33 | - @property | |
34 | - def ns(self): | |
35 | - return self._ns | |
36 | - |
@@ -1,93 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -"""This file contains AIGR-classes to model (all kind of) NameSpaces. | |
4 | - | |
5 | -There are several NameSpaces: the most prominent one is the ``Source_NS``, roughly the file that contains the (Castle) code. | |
6 | -""" | |
7 | -from __future__ import annotations | |
8 | - | |
9 | -__all__ = ['NameSpace', 'Source_NS'] | |
10 | - | |
11 | -import logging; logger = logging.getLogger(__name__) | |
12 | -import typing as PTH # Python TypeHints | |
13 | -from enum import Enum | |
14 | -from dataclasses import dataclass, KW_ONLY | |
15 | -from dataclasses import field as dc_field | |
16 | - | |
17 | -from .namednodes import NamedNode, NameError, ID | |
18 | - | |
19 | -from . import AIGR, _Marker | |
20 | - | |
21 | - | |
22 | -@dataclass | |
23 | -class NameSpace(NamedNode): | |
24 | - """This models a namespace (like a file, see ``Source_NS``). | |
25 | - | |
26 | - It contained *"named nodes"* that should be :method:`register()`ed and can be found by :method:`getID()` and/or :method:`findNode()`. | |
27 | - | |
28 | - XXX More""" | |
29 | - | |
30 | - _: KW_ONLY | |
31 | - _dict :PTH.Dict[ID, NamedNode]=dc_field(init=None, default_factory=lambda: dict()) #type: ignore[call-overload] | |
32 | - | |
33 | - def register(self, named_node :NamedNode, asName:PTH.Optional[ID|str]|str=None): | |
34 | - name = named_node.name if asName is None else ID(asName) | |
35 | - if name in self._dict: | |
36 | - old=self._dict[name] | |
37 | - logger.warning(f"The '{name}'-node is already in this namespace; -- it will be lost." + | |
38 | - f"Removed: {old}. New: {named_node}") | |
39 | - self._dict[name] = named_node | |
40 | - self._register_2ways(named_node) | |
41 | - | |
42 | - def _register_2ways(self, node): | |
43 | - node.register_in_NS(self) | |
44 | -### | |
45 | -### The following 3 methods are overkill. | |
46 | -### + findNode/getID only lock locally returning None (findNode) or raise NameError on no match | |
47 | -### + search is like findNode, but looks also in subNS'ses | |
48 | -### | |
49 | -### So, | |
50 | -### - ``NS.findNode(name)`` and ``NS.search(name)`` are equivalent | |
51 | -### (but search calls findNode, and can't be removed. find is also a better name) | |
52 | -###- There is no getID() for dottedName's | |
53 | -### | |
54 | - | |
55 | - | |
56 | - def findNode(self, name :ID|str) ->PTH.Optional[NamedNode]: | |
57 | - """Return the NamedNode with the specified name (aka ID), or None. | |
58 | - See :method:`getID` for an alternative""" | |
59 | - if not isinstance(name, ID): name=ID(name) | |
60 | - return self._dict.get(name, None) | |
61 | - | |
62 | - | |
63 | - def getID(self, name :ID) ->NamedNode: #Or raise NameError | |
64 | - """Return the NamedNode with the specified name (aka ID), or raised an NameError:AttributeError. | |
65 | - See :method:`findNode` for an alternative""" | |
66 | - node = self.findNode(name) | |
67 | - if node is None: | |
68 | - raise NameError(f"No node named {name} in NS:{self.name}") | |
69 | - return node | |
70 | - | |
71 | - def search(self, dottedName :ID) ->PTH.Optional[NamedNode]: | |
72 | - """Search the namespace for the 1st part of `dottedName`, then that NS for the next part, etc. And return the "deepest" node, or None""" | |
73 | - | |
74 | - parts = dottedName.split('.',maxsplit=1) # parts is [<name>, (<name>.)*] parts[1] can be absent, parts[0] always exist | |
75 | - node = self.findNode(parts[0]) | |
76 | - if len(parts) == 1: | |
77 | - return node | |
78 | - try: | |
79 | - return node.search(parts[1]) #type: ignore[union-attr] # Assume a NS, else raise | |
80 | - except AttributeError: #node isn't a search'able/NameSpace --> Not found --> return None | |
81 | - return None | |
82 | - | |
83 | - def find_byType(self, cls:type) ->dict[ID, NamedNode]: | |
84 | - return {name: node for name, node in self._dict.items() if isinstance(node, cls)} | |
85 | - | |
86 | - def all_NS(self) ->dict[ID, NamedNode]: | |
87 | - return self.find_byType(NameSpace) | |
88 | - | |
89 | - | |
90 | -@dataclass | |
91 | -class Source_NS(NameSpace): | |
92 | - _: KW_ONLY | |
93 | - source :PTH.Optional[str]=None |
@@ -1,100 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -""" See ./ReadMe.rst""" | |
4 | - | |
5 | -from __future__ import annotations | |
6 | -import typing as PTH # Python TypeHints | |
7 | -from enum import Enum | |
8 | -from dataclasses import dataclass, KW_ONLY | |
9 | -from dataclasses import field as dc_field | |
10 | -from . import AIGR | |
11 | -from .events import Event | |
12 | -from .aid import TypedParameter, Argument # Castle/AIGR types | |
13 | -from .namednodes import NamedNode, ID | |
14 | - | |
15 | - | |
16 | - | |
17 | -__all__ = ['ProtocolKind', 'Protocol', 'EventProtocol'] | |
18 | -# DataProtocol, StreamProtocol are added/implemented later | |
19 | -# Do Not Export: _RootProtocol | |
20 | -# How about: ProtocolWrapper? | |
21 | - | |
22 | -class ProtocolKind(Enum): | |
23 | - """There are several kinds (types) of protocols. | |
24 | - | |
25 | - This can be modeled by subclassing (in langueas that support it), or | |
26 | - by using a low-int (aka a enum) and save that in the struct""" | |
27 | - Unknown = 0 | |
28 | - Event = 1 | |
29 | - Data = 2 | |
30 | - Stream = 3 | |
31 | - _unset = -1 | |
32 | - | |
33 | -@dataclass | |
34 | -class Protocol(NamedNode): | |
35 | - """ .. note:: Use one of the subclasses -- Only Event is defined yet | |
36 | - .. todo:: Design: What is the `kind` self and the inherited ones are not the same? | |
37 | - overriding ProtocolKind.Unknown is always allowed | |
38 | - """ | |
39 | - _BASE: PTH.ClassVar=None # pragma: no mutate | |
40 | - | |
41 | - _: KW_ONLY | |
42 | - kind :ProtocolKind | |
43 | - based_on :PTH.Optional[Protocol]=dc_field(default_factory= lambda :Protocol._BASE) # pragma: no mutate | |
44 | - typedParameters :PTH.Optional[PTH.Sequence[TypedParameter]]=() | |
45 | - | |
46 | - | |
47 | -@dataclass # pragma: no mutate | |
48 | -class _RootProtocol(Protocol): | |
49 | - """This is the base protocol; it exist as we can't instantiate Protocol""" | |
50 | - | |
51 | -baseProtocol = _RootProtocol(ID("Protocol"), kind=ProtocolKind.Unknown, based_on=None) # pragma: no mutate | |
52 | -Protocol._BASE=baseProtocol | |
53 | - | |
54 | -@dataclass # pragma: no mutate | |
55 | -class DataProtocol(Protocol): pass ### XXX ToDo (not exported) | |
56 | -@dataclass # pragma: no mutate | |
57 | -class StreamProtocol(Protocol): pass ### XXX ToDo (not exported) | |
58 | - | |
59 | - | |
60 | -@dataclass # pragma: no mutate | |
61 | -class ProtocolWrapper(Protocol): | |
62 | - _: KW_ONLY | |
63 | - kind : ProtocolKind=ProtocolKind._unset | |
64 | - arguments: PTH.Sequence[Argument] | |
65 | - | |
66 | - def __post_init__(self): | |
67 | - if self.kind is ProtocolKind._unset: | |
68 | - self.kind = self.based_on.kind | |
69 | - if self.name == "": | |
70 | - self.name = f"Wrapper for {self.based_on.name}({self.arguments})" ### | |
71 | - | |
72 | - def __getattr__(self, name): #delegate to base XXX move to Wrapper Base? | |
73 | - return getattr(self.based_on, name) | |
74 | - | |
75 | - | |
76 | -@dataclass # pragma: no mutate | |
77 | -class EventProtocol(Protocol): | |
78 | - """An event-based protocol is basically a set of events. | |
79 | - | |
80 | - This recorded as an dyn-array of the new event; there is no need to copy the inherited ones | |
81 | - """ | |
82 | - _: KW_ONLY | |
83 | - kind: ProtocolKind = ProtocolKind.Event | |
84 | - events: PTH.Sequence[Event] | |
85 | - #redefine EventProtocol always inherit from an EventProtocol (or Wrapper) | |
86 | - based_on: EventProtocol|ProtocolWrapper = dc_field(default_factory= lambda :Protocol._BASE) | |
87 | - | |
88 | - def _noEvents(self): | |
89 | - inherited = self.based_on._noEvents() if isinstance(self.based_on, EventProtocol) else 0 | |
90 | - return inherited + len(self.events) | |
91 | - | |
92 | - def eventIndex(self, event: Event) -> int: | |
93 | - try: | |
94 | - return self.based_on.eventIndex(event) | |
95 | - except ValueError: # not inherited | |
96 | - inherited_events = self.based_on._noEvents() | |
97 | - except AttributeError: # self.based_on === None | |
98 | - inherited_events = 0 | |
99 | - # `event` not inherited: search locally and number of inherited events | |
100 | - return self.events.index(event) + inherited_events |
@@ -1,28 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -import pytest | |
4 | - | |
5 | -from castle.aigr import AIGR | |
6 | - | |
7 | -class Demo(AIGR): | |
8 | - pass | |
9 | - | |
10 | -def test_noAIGR(): | |
11 | - try: | |
12 | - AIGR() | |
13 | - assert False , "shouldn't be able to initiate an AIGR directly" # pragma: no cover | |
14 | - except NotImplementedError: | |
15 | - pass | |
16 | - | |
17 | -def test_AIGR_sub(): | |
18 | - d = Demo() | |
19 | - assert isinstance(d, AIGR) | |
20 | - | |
21 | -@pytest.mark.skip("The ID comes A bit later") | |
22 | -def test_ID_many(): | |
23 | - assert False, """Make an ID class in AIGR (or generic). ((also see NamedNode)) | |
24 | - | |
25 | - It holds the name in AIGR -nodes ipv de str class. Model it a bit like pathlib.Path | |
26 | - | |
27 | - We should have atomic_ID, relative_ID, DottedName_ID, ect | |
28 | - """ |
@@ -1,12 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -import logging; logger = logging.getLogger(__name__) | |
4 | -import pytest | |
5 | - | |
6 | -from castle.aigr import aid | |
7 | - | |
8 | -def test_Argument_with_NoName(): | |
9 | - a = aid.Argument(value=1) | |
10 | - | |
11 | - assert a.name == None | |
12 | - assert a.value == 1 |
@@ -1,22 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -from castle.aigr import Event | |
4 | -from castle.aigr.aid import TypedParameter | |
5 | - | |
6 | - | |
7 | -def test_0_Event_empty(): | |
8 | - e = Event("leeg_event") | |
9 | - assert e.name == 'leeg_event' | |
10 | - assert e.return_type is None | |
11 | - assert len(e.typedParameters) == 0 | |
12 | - | |
13 | -def test_1_Event_small(): | |
14 | - e = Event("demo_int", typedParameters=[TypedParameter(name='p1', type=float )]) | |
15 | - assert e.return_type is None | |
16 | - assert len(e.typedParameters) == 1 | |
17 | - assert e.typedParameters[0].name == 'p1' | |
18 | - assert e.typedParameters[0].type == float | |
19 | - | |
20 | -def test_2_Event_retunInt(): | |
21 | - e = Event("an_event", return_type=int) | |
22 | - assert e.return_type == int |
@@ -1,24 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2022, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -from castle.aigr import ProtocolKind | |
4 | - | |
5 | - | |
6 | -def test_0_Unknow_value(): | |
7 | - assert ProtocolKind.Unknown.value ==0 | |
8 | - | |
9 | -def test_O_unset_value(): | |
10 | - assert ProtocolKind._unset.value < 0 | |
11 | - | |
12 | -def test_1_NameExist(): | |
13 | - assert ProtocolKind.Unknown | |
14 | - assert ProtocolKind.Event | |
15 | - assert ProtocolKind.Data | |
16 | - assert ProtocolKind.Stream | |
17 | - | |
18 | -def test_2_ConsecutiveSeries(): | |
19 | - values = sorted(k.value for k in ProtocolKind) | |
20 | - v1 = values[0] | |
21 | - for v2 in values[1:]: | |
22 | - assert v1+1==v2, f"{ProtocolKind(v1)}+1 <> {ProtocolKind(v2)}" | |
23 | - v1=v2 | |
24 | - |
@@ -1,95 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2022, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -import logging; logger = logging.getLogger(__name__) | |
4 | -import pytest | |
5 | - | |
6 | -from castle.aigr import Protocol, ProtocolKind | |
7 | -from castle.aigr import Event, EventProtocol | |
8 | -from castle.aigr.aid import TypedParameter | |
9 | - | |
10 | - | |
11 | -@pytest.fixture | |
12 | -def emptyProtocol(): | |
13 | - return EventProtocol("EMPTY", events=[], based_on=None) | |
14 | - | |
15 | -@pytest.fixture | |
16 | -def emptyProtocol_baseNotSet(): | |
17 | - return Protocol("VeryEmpty", kind=ProtocolKind.Unknown) | |
18 | - | |
19 | -@pytest.fixture | |
20 | -def anEvent(): | |
21 | - return Event("input", typedParameters=[TypedParameter(name='event', type=int)]) | |
22 | - | |
23 | -@pytest.fixture | |
24 | -def simpleSieve(anEvent): | |
25 | - return EventProtocol("SimpleSieve", events=[anEvent]) | |
26 | - | |
27 | - | |
28 | - | |
29 | -def test_1_isEvent(emptyProtocol, simpleSieve): | |
30 | - assert emptyProtocol.kind == ProtocolKind.Event | |
31 | - assert simpleSieve.kind == ProtocolKind.Event | |
32 | - | |
33 | - | |
34 | -def test_2a_based_onRoot(emptyProtocol): | |
35 | - import castle.aigr.protocols | |
36 | - emptyProtocol.based_on is castle.aigr.protocols._RootProtocol | |
37 | - | |
38 | -def test_2b_based_onRoot_notSet(emptyProtocol_baseNotSet): | |
39 | - import castle.aigr.protocols | |
40 | - emptyProtocol_baseNotSet.based_on is castle.aigr.protocols._RootProtocol | |
41 | - | |
42 | - | |
43 | -def test_3a_eventIndex_empty(emptyProtocol, anEvent): | |
44 | - assert emptyProtocol._noEvents() == 0 | |
45 | - try: | |
46 | - emptyProtocol.eventIndex(anEvent) | |
47 | - assert False, f"{anEvent} shouldn't be in the emptyProtocol" # pragma: no cover | |
48 | - except ValueError: pass | |
49 | - | |
50 | - | |
51 | -def test_3b_eventIndex_simple(simpleSieve, anEvent): | |
52 | - assert simpleSieve._noEvents() == 1 | |
53 | - assert simpleSieve.eventIndex(anEvent) == 0, f"`anEvent` should be eventIndex==0, but isn;t...T\n {anEvent}\n{simpleSieve.events}" | |
54 | - | |
55 | - | |
56 | -def test_3c_eventIndex_inherited(): | |
57 | - e0 = Event("E0") | |
58 | - e1 = Event("E1") | |
59 | - e2 = Event("E2") | |
60 | - p0 = EventProtocol("P0", events=[e0]) | |
61 | - p1 = EventProtocol("P1", events=[e1,e2], based_on=p0) | |
62 | - | |
63 | - assert p1._noEvents() == 3 | |
64 | - assert p1.eventIndex(e2) == 2 | |
65 | - | |
66 | - | |
67 | -def test_protocol_with_Noparms(emptyProtocol): | |
68 | - assert emptyProtocol.typedParameters == () | |
69 | - | |
70 | - | |
71 | -def test_protocol_with_aParm(): | |
72 | - e = EventProtocol("With_a_parm", events=[], based_on=None, | |
73 | - typedParameters=[TypedParameter(name='p', type=float)]) | |
74 | - assert len(e.typedParameters) ==1 | |
75 | - assert e.typedParameters[0].name == 'p' | |
76 | - assert e.typedParameters[0].type == float | |
77 | - | |
78 | - | |
79 | -def test_protocol_with_4Parms(): | |
80 | - e = EventProtocol("With_4_Parms", events=[], based_on=None, | |
81 | - typedParameters=( | |
82 | - TypedParameter(name='p0', type=float ), | |
83 | - TypedParameter(name='p1', type=int ), | |
84 | - TypedParameter(name='p2', type=str ), | |
85 | - TypedParameter(name='p3', type=None ), | |
86 | - )) | |
87 | - assert len(e.typedParameters) == 4 | |
88 | - assert (e.typedParameters[0].name, e.typedParameters[0].type) == ('p0', float) | |
89 | - assert (e.typedParameters[1].name, e.typedParameters[1].type) == ('p1', int) | |
90 | - assert (e.typedParameters[2].name, e.typedParameters[2].type) == ('p2', str) | |
91 | - assert (e.typedParameters[3].name, e.typedParameters[3].type) == ('p3', None) | |
92 | - | |
93 | - | |
94 | -#Note: for more complicated cases, see :file:`test_2c_WrappedProtocols.py` | |
95 | - |
@@ -1,119 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -""" With :file:`test_2b_protocol.py` most/all normal cases are verified. This file focus on **GenericProtocols**: | |
4 | - GenericProtocols are protocols that inherited from a protocol with parameters. We focus on EventProtocols, but it | |
5 | - will be entende to all protocols. | |
6 | - | |
7 | - .. code-block: Castle: | |
8 | - | |
9 | - protocol SlowStart(queue_max:int): EventProtocol ... | |
10 | - protocol SimpleSieve : SlowStart(1) ... | |
11 | - | |
12 | - * `SlowStart` is a **Generic Protocol** with a (1) parameter: the (initial) (max) size of the queue. | |
13 | - * `SimpleSieve` used that protocol, and set that parameter to 1. | |
14 | - | |
15 | - The parameter to SlowStart, in SimpleSieve, can be seen as a template-specialisation as it is called in C++ | |
16 | - | |
17 | - .. code-block: C++ | |
18 | - | |
19 | - // A C++ Template approximation of above: using classes not protocols ... | |
20 | - template <int queue_max> | |
21 | - class SlowStart {...} | |
22 | - | |
23 | - class SimpleSieve : SlowStart<1> { ...} | |
24 | - | |
25 | - In both examples the value ``1`` is filled in (aka hard-coded) into the implementation of SlowStart: Wherever | |
26 | - `queue_max` is used the value `1` is used -- as if the source always has had a `1`.... | |
27 | - | |
28 | - .. note:: The Castle syntax uses parentheses, as for normal (formal) arguments, not chevrons <angle brackets> as in C++ | |
29 | - | |
30 | - It read as-if SlowStart is instantiated (as in Python: calling ``SlowStart()``), but actually it is | |
31 | - **specialised*, by filling in the template/parentheses. The result is a not an instance of SlowStart, but a "new | |
32 | - Protocol, which kind-of inherits from SlowStart -- and SimpleSieve inherits from that one. | |
33 | - | |
34 | - This *syntax detail* is handled in the parser! | |
35 | - | |
36 | - In the AIGR, the specialised *SlowStart(1)* protocol is modeled by a ProtocolWrapper; which in placed in-between | |
37 | - (the generic) Slowstart and SimpleSieve. | |
38 | - | |
39 | - .. hint:: (implementation of Generic Protocols) | |
40 | - | |
41 | - With ``functools.partial``, it seams possible to make a "partial class". BUT, that is NOT VALID RPython! | |
42 | - (see: .../PyPy+Rpython/new/partialCLass.py). | |
43 | - | |
44 | - So, it will be implemented in normal the normale AST-->AIGT-->AIGT-->RPY train (as in C++ template's): just fill in | |
45 | -""" | |
46 | - | |
47 | -import logging; logger = logging.getLogger(__name__) | |
48 | -import pytest | |
49 | - | |
50 | -from castle.aigr import Protocol, ProtocolKind | |
51 | -from castle.aigr import Event, EventProtocol | |
52 | -from castle.aigr.aid import TypedParameter, Argument | |
53 | -from castle.aigr.protocols import ProtocolWrapper | |
54 | - | |
55 | -""" There are a few cases | |
56 | -///CastleCode | |
57 | - protocol Base(queue_max:int): EventProtocol | |
58 | - protocol Sub_a: Base(queue_max=1) # (a): named argument | |
59 | - protocol Sub_b: Base(1) # (b): positional arg | |
60 | - | |
61 | -""" | |
62 | - | |
63 | - | |
64 | -@pytest.fixture | |
65 | -def base(): | |
66 | - return EventProtocol("Base", events=[], typedParameters=[TypedParameter(name='queue_max', type=int)]) | |
67 | - | |
68 | -@pytest.fixture | |
69 | -def sub_a(base): | |
70 | - return EventProtocol("Sub_a", events=[], based_on=ProtocolWrapper(name="", based_on=base, arguments=(Argument(name='queue_max', value=1),))) | |
71 | - | |
72 | -@pytest.fixture | |
73 | -def sub_b(base): | |
74 | - return EventProtocol("Sub_b", events=[], based_on=ProtocolWrapper("", based_on=base, arguments=(Argument(value=1),))) | |
75 | - | |
76 | -def assert_GP_kind(base, sub): | |
77 | - assert sub.kind == base.kind | |
78 | - assert sub.based_on.kind == base.kind | |
79 | - assert sub.based_on.based_on is base | |
80 | - | |
81 | -def test_GenericProtocol_kind_a(base, sub_a): | |
82 | - assert_GP_kind(base, sub_a) | |
83 | - | |
84 | -def test_GenericProtocol_kind_b(base, sub_b): | |
85 | - assert_GP_kind(base, sub_b) | |
86 | - | |
87 | - | |
88 | -def assert_GP_name(base, sub): | |
89 | - assert "Wrapper" in sub.based_on.name | |
90 | - assert "Base" in sub.based_on.name | |
91 | - | |
92 | -def test_GenericProtocol_name_a(base, sub_a): | |
93 | - assert_GP_name(base, sub_a) | |
94 | - assert "queue_max" in sub_a.based_on.name # the argument-name is only in the (a) version | |
95 | - | |
96 | -def test_GenericProtocol_name_b(base, sub_b): | |
97 | - assert_GP_name(base, sub_b) | |
98 | - assert "queue_max" not in sub_b.based_on.name # the argument-name is only in the (a) version | |
99 | - | |
100 | - | |
101 | - | |
102 | -def test_strange_1(base): | |
103 | - """This is (very) atypical use -- but it helps to get coverage""" | |
104 | - sub_strange = EventProtocol("SubStrange", events=[], based_on=ProtocolWrapper(name="Strange", | |
105 | - kind=42, | |
106 | - based_on=base, | |
107 | - arguments=(Argument(value=1),))) | |
108 | - assert sub_strange.based_on.name == "Strange" | |
109 | - assert sub_strange.based_on.kind == 42, "When we set a strange kind-number it should be stored" | |
110 | - | |
111 | -def test_strange_2(base): | |
112 | - """This is (very) atypical use -- but it helps to get coverage""" | |
113 | - sub_strange = EventProtocol("SubStrange", events=[], based_on=ProtocolWrapper(name="Strange", | |
114 | - kind=None, | |
115 | - based_on=base, | |
116 | - arguments=(Argument(value=1),))) | |
117 | - | |
118 | - assert sub_strange.based_on.name == "Strange" | |
119 | - |
@@ -1,155 +0,0 @@ | ||
1 | -# (C) Albert Mietus, 2023. Part of Castle/CCastle project | |
2 | - | |
3 | -import logging; logger = logging.getLogger(__name__) | |
4 | -import pytest | |
5 | -import typing as PTH # Python TypeHints | |
6 | -from dataclasses import dataclass, KW_ONLY | |
7 | -from random import randint | |
8 | - | |
9 | -from castle.aigr import NameSpace, Source_NS | |
10 | - | |
11 | -from castle.aigr.namespaces import NamedNode ## May be moved ip | |
12 | -from castle.aigr.namespaces import NameError | |
13 | - | |
14 | -@dataclass | |
15 | -class DummyNode(NamedNode): | |
16 | - name :str | |
17 | - _: KW_ONLY | |
18 | - dummy :PTH.Any=None | |
19 | - | |
20 | -@pytest.fixture | |
21 | -def a_node(): | |
22 | - return DummyNode("a_node", dummy=randint(42,2023)) | |
23 | - | |
24 | -@pytest.fixture | |
25 | -def aNS(a_node): | |
26 | - ns = NameSpace("aNS") | |
27 | - ns.register(a_node) | |
28 | - return ns | |
29 | - | |
30 | -@pytest.fixture | |
31 | -def top(): | |
32 | - top = NameSpace('top') | |
33 | - return top | |
34 | - | |
35 | -@pytest.fixture | |
36 | -def sub(top): | |
37 | - sub = NameSpace('sub') | |
38 | - top.register(sub) | |
39 | - return sub | |
40 | - | |
41 | -@pytest.fixture | |
42 | -def sourceNS(a_node): | |
43 | - ns = Source_NS("sourceNS", source="dummy") | |
44 | - ns.register(a_node) | |
45 | - return ns | |
46 | - | |
47 | - | |
48 | -def test_1_NS_stored(a_node, aNS): | |
49 | - name = a_node.name | |
50 | - assert aNS.getID(name) is a_node | |
51 | - assert aNS.findNode(name) is a_node | |
52 | - | |
53 | - | |
54 | -def test_2_NS_find_vs_get_when_not_registered(aNS): | |
55 | - assert aNS.findNode("Deze Bestaat Niet") is None | |
56 | - try: | |
57 | - aNS.getID("Deze Bestaat Niet") | |
58 | - assert False, """`aNS.getID("Deze Bestaat Niet")` should raise an error""" | |
59 | - except NameError: pass | |
60 | - | |
61 | - | |
62 | -def test_3_sourceNS_combi(a_node, sourceNS): | |
63 | - "The functionality as shown in _NS1 & _NS2 should also work with Source_NS" | |
64 | - name = a_node.name | |
65 | - assert sourceNS.getID(name) is a_node | |
66 | - assert sourceNS.findNode(name) is a_node | |
67 | - | |
68 | - assert sourceNS.findNode("Deze Bestaat Niet") is None | |
69 | - try: | |
70 | - sourceNS.getID("Deze Bestaat Niet") | |
71 | - assert False, """`sourceNS.getID("Deze Bestaat Niet")` should raise an error""" | |
72 | - except NameError: pass | |
73 | - | |
74 | - | |
75 | -def test_4_sameName_is_replaced(aNS): | |
76 | - logger.warning("""NOTICE: This test will issue the warning 'astle.aigr.namespaces:namespaces.py:42' You should ignore it""") | |
77 | - name='TriggerWarning' | |
78 | - one = DummyNode(name, dummy='one') | |
79 | - two = DummyNode(name, dummy='one') | |
80 | - aNS.register(one); assert aNS.getID(name) is one #No test, just verify | |
81 | - | |
82 | - aNS.register(two) | |
83 | - assert aNS.getID(name) is two #The test | |
84 | - | |
85 | - | |
86 | -def test_5a_ns_in_ns(): | |
87 | - "when we import a NS, we get a NS in a NS ..." | |
88 | - top = NameSpace('top') | |
89 | - sub = NameSpace('sub') | |
90 | - elm = DummyNode('elm', dummy="with.dotted.Name") | |
91 | - top.register(sub) | |
92 | - sub.register(elm) | |
93 | - | |
94 | - assert top.getID('sub') is sub | |
95 | - assert sub.getID('elm') is elm | |
96 | - assert top.search(dottedName="sub.elm") is elm | |
97 | - | |
98 | - | |
99 | -def test_5b_seach_1level(aNS,a_node): | |
100 | - name = a_node.name | |
101 | - assert (aNS.search(name) is a_node) and (aNS.getID(name) is a_node), "search should find that what getID returns" | |
102 | - | |
103 | - | |
104 | -def test_5c_seachNotFound_1(top): | |
105 | - assert top.search("Deze bestaat niet") is None | |
106 | - | |
107 | -def test_5d_seachNotFound_sub(top, sub): | |
108 | - assert top.search("top.Deze.bestaat.niet") is None | |
109 | - | |
110 | -def test_6a_registered_is_2ways(aNS, a_node): | |
111 | - """When a NamedNode is registered in a NameSpace, it should a backlink (`ns property) to the NS again""" | |
112 | - assert a_node.ns is aNS | |
113 | - | |
114 | -def test_6b_registered_is_2ways_once(aNS, a_node): | |
115 | - """Currently, a NamedNode can be registered in multiple namespaces, but the backlink is always the last | |
116 | - XXX ToDo: is that the intent? For now test as is""" | |
117 | - name = a_node.name | |
118 | - other = NameSpace('other') | |
119 | - other.register(a_node) | |
120 | - | |
121 | - assert (aNS.getID(name) is a_node) and (other.getID(name) is a_node), "A NamedNode can be registered in two NS'ses ..." | |
122 | - assert a_node.ns is other, " ...but Only the last NS is remembered" | |
123 | - | |
124 | -def test_7_alias(aNS): | |
125 | - node=DummyNode("aliased") | |
126 | - alias="anOtherName" | |
127 | - aNS.register(node, asName=alias) | |
128 | - assert aNS.findNode(name=alias) is node, f"it should be registered with the given alias: {alias}" | |
129 | - assert aNS.findNode(name=node.name) is None, f"The realname should not be registered" | |
130 | - | |
131 | - | |
132 | -def test_byType_None(aNS): | |
133 | - d = aNS.find_byType(type(None)) # There should be None's in aNS | |
134 | - assert isinstance(d, dict) | |
135 | - assert len(d)==0 | |
136 | - | |
137 | -def test_byType_Dummy(aNS, a_node): | |
138 | - d = aNS.find_byType(DummyNode) | |
139 | - assert len(d)==1 | |
140 | - assert a_node.name in d | |
141 | - assert d[a_node.name] is a_node # note: this assumed no aliasses are used ('asName') | |
142 | - | |
143 | -def test_byType_NS(top, sub, sourceNS): | |
144 | - top.register(sourceNS) # Note: sub is already 'in; top | |
145 | - | |
146 | - d = top.find_byType(NameSpace) | |
147 | - assert len(d) == 2 # sub, sourceNS | |
148 | - assert d['sub'] is sub | |
149 | - assert d['sourceNS'] is sourceNS | |
150 | - | |
151 | - | |
152 | - | |
153 | -@pytest.mark.skip("Todo: Unite `.search()` and `.find()` [& `.getID()] -- see comment in `aigr/namespaces.py`") | |
154 | -def test_ToDo_Unite(): | |
155 | - assert False |