• R/O
  • SSH

Commit

Tags
Keine Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Castle: The best Real-Time/Embedded/HighTech language EVER. Attempt 2


Commit MetaInfo

Revisionc12c2e97c6f8e762bde8da07a08c4c8f9acbfb9d (tree)
Zeit2024-02-19 06:43:03
AutorAlbert Mietus < albert AT mietus DOT nl >
CommiterAlbert Mietus < albert AT mietus DOT nl >

Log Message

moved aigr into base_packages/castle-aigr (code & test) TODO: Makefile, setup, ...

Ändern Zusammenfassung

Diff

diff -r 31bc9e9ca84f -r c12c2e97c6f8 .hgignore
--- a/.hgignore Sun Feb 18 21:42:52 2024 +0100
+++ b/.hgignore Sun Feb 18 22:43:03 2024 +0100
@@ -21,3 +21,4 @@
2121 html
2222 Icon
2323 DocParts/Design/*/*.png
24+_setup.py
\ No newline at end of file
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/_ReadMe.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/_ReadMe.rst Sun Feb 18 22:43:03 2024 +0100
@@ -0,0 +1,5 @@
1+CCastle2/castle/aigr/_ReadMe.rst
2+================================
3+
4+
5+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/__init__.py Sun Feb 18 22:43:03 2024 +0100
@@ -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 *
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/aid.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/aid.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/events.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/events.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/interfaces.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/interfaces.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/namednodes.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/namednodes.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/namespaces.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/namespaces.py Sun Feb 18 22:43:03 2024 +0100
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/castle/aigr/protocols.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/castle/aigr/protocols.py Sun Feb 18 22:43:03 2024 +0100
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_0_AIGR.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_0_AIGR.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+ """
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_0_aid.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_0_aid.py Sun Feb 18 22:43:03 2024 +0100
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_1_events.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_1_events.py Sun Feb 18 22:43:03 2024 +0100
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_2a_protocolKind.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_2a_protocolKind.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_2b_protocol.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_2b_protocol.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_2c_GenericProtocols.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_2c_GenericProtocols.py Sun Feb 18 22:43:03 2024 +0100
@@ -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+
diff -r 31bc9e9ca84f -r c12c2e97c6f8 base_packages/castle-aigr/pytst/test_3_namespaces.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base_packages/castle-aigr/pytst/test_3_namespaces.py Sun Feb 18 22:43:03 2024 +0100
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/_ReadMe.rst
--- a/castle/aigr/_ReadMe.rst Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
1-CCastle2/castle/aigr/_ReadMe.rst
2-================================
3-
4-
5-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/__init__.py
--- a/castle/aigr/__init__.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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 *
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/aid.py
--- a/castle/aigr/aid.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/events.py
--- a/castle/aigr/events.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/interfaces.py
--- a/castle/aigr/interfaces.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/namednodes.py
--- a/castle/aigr/namednodes.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/namespaces.py
--- a/castle/aigr/namespaces.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 castle/aigr/protocols.py
--- a/castle/aigr/protocols.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_0_AIGR.py
--- a/pytst/aigr/test_0_AIGR.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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- """
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_0_aid.py
--- a/pytst/aigr/test_0_aid.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_1_events.py
--- a/pytst/aigr/test_1_events.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_2a_protocolKind.py
--- a/pytst/aigr/test_2a_protocolKind.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_2b_protocol.py
--- a/pytst/aigr/test_2b_protocol.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_2c_GenericProtocols.py
--- a/pytst/aigr/test_2c_GenericProtocols.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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-
diff -r 31bc9e9ca84f -r c12c2e97c6f8 pytst/aigr/test_3_namespaces.py
--- a/pytst/aigr/test_3_namespaces.py Sun Feb 18 21:42:52 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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