diff --git a/pyskom/client/async_types.py b/pyskom/client/async_types.py new file mode 100644 index 0000000000000000000000000000000000000000..1b3eec2b58b46f2f9f69014481e1e54353c4ac51 --- /dev/null +++ b/pyskom/client/async_types.py @@ -0,0 +1,376 @@ +# This file was generated at 2025-02-14 19:54:32 by tools.codegen +# Generated from Protocol A 11.1 +# Run `python -m tools.codegen` with project root as CWD to regenerate +# For your own sake please do not edit this file as it will be overwritten + +from dataclasses import dataclass +from enum import Enum +from typing import Union, Self, Any, ClassVar + +from .kom_types import KomType, SimpleType +from .simple_types import KomInt8, KomInt16, KomInt32, KomStr, KomBool,KomFloat +from .parser import MessageParser +from .writer import MessageWriter + + +from .types import ( + TextNo, + WhoInfo, + AuxItem, + SessionNo, + InfoType, + TextStat, + ConfNo, + PersNo +) + +@dataclass(slots=True) +class AsyncNewName: + conf_no: 'ConfNo' + old_name: KomStr + new_name: KomStr + message_nr: ClassVar[int] = 5 + + def write(self, writer: MessageWriter): + writer.write_int(self.conf_no) + writer.write_str(self.old_name) + writer.write_str(self.new_name) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_str(), + parser.read_str() + ) + + +@dataclass(slots=True) +class AsyncIAmOn: + info: 'WhoInfo' + message_nr: ClassVar[int] = 6 + + def write(self, writer: MessageWriter): + self.info.write(writer) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + WhoInfo.parse(parser) + ) + + +@dataclass(slots=True) +class AsyncSyncDb: + message_nr: ClassVar[int] = 7 + + def write(self, writer: MessageWriter): + pass + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls() + + +@dataclass(slots=True) +class AsyncLeaveConf: + conf_no: 'ConfNo' + message_nr: ClassVar[int] = 8 + + def write(self, writer: MessageWriter): + writer.write_int(self.conf_no) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16() + ) + + +@dataclass(slots=True) +class AsyncLogin: + pers_no: 'PersNo' + session_no: 'SessionNo' + message_nr: ClassVar[int] = 9 + + def write(self, writer: MessageWriter): + writer.write_int(self.pers_no) + writer.write_int(self.session_no) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int32() + ) + + +@dataclass(slots=True) +class AsyncRejectedConnection: + message_nr: ClassVar[int] = 11 + + def write(self, writer: MessageWriter): + pass + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls() + + +@dataclass(slots=True) +class AsyncSendMessage: + recipient: 'ConfNo' + sender: 'PersNo' + message: KomStr + message_nr: ClassVar[int] = 12 + + def write(self, writer: MessageWriter): + writer.write_int(self.recipient) + writer.write_int(self.sender) + writer.write_str(self.message) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int16(), + parser.read_str() + ) + + +@dataclass(slots=True) +class AsyncLogout: + pers_no: 'PersNo' + session_no: 'SessionNo' + message_nr: ClassVar[int] = 13 + + def write(self, writer: MessageWriter): + writer.write_int(self.pers_no) + writer.write_int(self.session_no) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int32() + ) + + +@dataclass(slots=True) +class AsyncDeletedText: + text_no: 'TextNo' + text_stat: 'TextStat' + message_nr: ClassVar[int] = 14 + + def write(self, writer: MessageWriter): + writer.write_int(self.text_no) + self.text_stat.write(writer) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int32(), + TextStat.parse(parser) + ) + + +@dataclass(slots=True) +class AsyncNewText: + text_no: 'TextNo' + text_stat: 'TextStat' + message_nr: ClassVar[int] = 15 + + def write(self, writer: MessageWriter): + writer.write_int(self.text_no) + self.text_stat.write(writer) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int32(), + TextStat.parse(parser) + ) + + +@dataclass(slots=True) +class AsyncNewRecipient: + text_no: 'TextNo' + conf_no: 'ConfNo' + type: 'InfoType' + message_nr: ClassVar[int] = 16 + + def write(self, writer: MessageWriter): + writer.write_int(self.text_no) + writer.write_int(self.conf_no) + self.type.write(writer) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int32(), + parser.read_int16(), + InfoType.parse(parser) + ) + + +@dataclass(slots=True) +class AsyncSubRecipient: + text_no: 'TextNo' + conf_no: 'ConfNo' + type: 'InfoType' + message_nr: ClassVar[int] = 17 + + def write(self, writer: MessageWriter): + writer.write_int(self.text_no) + writer.write_int(self.conf_no) + self.type.write(writer) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int32(), + parser.read_int16(), + InfoType.parse(parser) + ) + + +@dataclass(slots=True) +class AsyncNewMembership: + pers_no: 'PersNo' + conf_no: 'ConfNo' + message_nr: ClassVar[int] = 18 + + def write(self, writer: MessageWriter): + writer.write_int(self.pers_no) + writer.write_int(self.conf_no) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int16() + ) + + +@dataclass(slots=True) +class AsyncNewUserArea: + pers_no: 'PersNo' + old_user_area: 'TextNo' + new_user_area: 'TextNo' + message_nr: ClassVar[int] = 19 + + def write(self, writer: MessageWriter): + writer.write_int(self.pers_no) + writer.write_int(self.old_user_area) + writer.write_int(self.new_user_area) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int32(), + parser.read_int32() + ) + + +@dataclass(slots=True) +class AsyncNewPresentation: + conf_no: 'ConfNo' + old_presentation: 'TextNo' + new_presentation: 'TextNo' + message_nr: ClassVar[int] = 20 + + def write(self, writer: MessageWriter): + writer.write_int(self.conf_no) + writer.write_int(self.old_presentation) + writer.write_int(self.new_presentation) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int32(), + parser.read_int32() + ) + + +@dataclass(slots=True) +class AsyncNewMotd: + conf_no: 'ConfNo' + old_motd: 'TextNo' + new_motd: 'TextNo' + message_nr: ClassVar[int] = 21 + + def write(self, writer: MessageWriter): + writer.write_int(self.conf_no) + writer.write_int(self.old_motd) + writer.write_int(self.new_motd) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int16(), + parser.read_int32(), + parser.read_int32() + ) + + +@dataclass(slots=True) +class AsyncTextAuxChanged: + text_no: 'TextNo' + deleted: list['AuxItem'] + added: list['AuxItem'] + message_nr: ClassVar[int] = 22 + + def write(self, writer: MessageWriter): + writer.write_int(self.text_no) + writer.write_array_type(self.deleted) + writer.write_array_type(self.added) + + @classmethod + def parse(cls, parser: MessageParser) -> Self: + return cls( + parser.read_int32(), + parser.read_array_type(AuxItem.parse), + parser.read_array_type(AuxItem.parse) + ) + + +ASYNC_TYPES = { + 5: AsyncNewName, + 6: AsyncIAmOn, + 7: AsyncSyncDb, + 8: AsyncLeaveConf, + 9: AsyncLogin, + 11: AsyncRejectedConnection, + 12: AsyncSendMessage, + 13: AsyncLogout, + 14: AsyncDeletedText, + 15: AsyncNewText, + 16: AsyncNewRecipient, + 17: AsyncSubRecipient, + 18: AsyncNewMembership, + 19: AsyncNewUserArea, + 20: AsyncNewPresentation, + 21: AsyncNewMotd, + 22: AsyncTextAuxChanged +} + +class AsyncMessageNr: + NEW_NAME = 5 + I_AM_ON = 6 + SYNC_DB = 7 + LEAVE_CONF = 8 + LOGIN = 9 + REJECTED_CONNECTION = 11 + SEND_MESSAGE = 12 + LOGOUT = 13 + DELETED_TEXT = 14 + NEW_TEXT = 15 + NEW_RECIPIENT = 16 + SUB_RECIPIENT = 17 + NEW_MEMBERSHIP = 18 + NEW_USER_AREA = 19 + NEW_PRESENTATION = 20 + NEW_MOTD = 21 + TEXT_AUX_CHANGED = 22 \ No newline at end of file diff --git a/pyskom/client/client.py b/pyskom/client/client.py index 72176e894a9169548c0d2d4ac20fc244c5e853be..09b8437ddf9ecfb46d2299fae383c1dd9c8a0656 100644 --- a/pyskom/client/client.py +++ b/pyskom/client/client.py @@ -1,4 +1,4 @@ -# This file was generated at 2025-02-14 13:23:36 by tools.codegen +# This file was generated at 2025-02-14 19:54:32 by tools.codegen # Generated from 11.1 # Run `python -m tools.codegen` with project root as CWD to regenerate # For your own sake please do not edit this file as it will be overwritten diff --git a/pyskom/client/client_base.py b/pyskom/client/client_base.py index a4a9fda3467b85914ff1cf5622b5556d8f9effbc..cd18c2f07d5db857572d33d8da6d1162a27f3763 100644 --- a/pyskom/client/client_base.py +++ b/pyskom/client/client_base.py @@ -4,18 +4,21 @@ from asyncio import StreamReader, StreamWriter, Future from logging import getLogger from typing import Any, Callable, Awaitable +from .async_types import ASYNC_TYPES from .errors import ERRORS from .kom_error import InvalidErrorCodeError from .parser import MessageParser, parse_kom_type from .types import Info, ConfZInfo, Conference, ConfType from .writer import to_hollerith, MessageWriter, write_kom_type -from .kom_types import KomType, SimpleType, KomTypeGroup +from .kom_types import KomType, SimpleType, KomTypeGroup, AsyncMessage logger = getLogger("client") + class CallTimeoutError(Exception): pass + class ClientBase: def __init__(self, host: str, connection_info: str, port: int = 4894): self.host: str = host @@ -28,10 +31,8 @@ class ClientBase: self.server_encoding = "iso-8859-1" self._next_ref = 0 self._calls: dict[int, tuple[Future, Callable[[MessageParser | None], Any]]] = {} - self._on_connected_hooks: list[Callable[[], Awaitable[None]]] = [] - - def on_connected(self, f: Callable[[], Awaitable[None]]): - self._on_connected_hooks.append(f) + self._on_connected_hook: Callable[[], Awaitable[None]] | None = None + self._on_event_hook: Callable[[AsyncMessage], Awaitable[None]] | None = None async def on_packet(self, packet: bytes): parser = MessageParser(packet, cursor=0) @@ -39,16 +40,16 @@ class ClientBase: reply_type = parser.read_reply() if reply_type is None: break - elif reply_type == 61: # = (result ok) + elif reply_type == 61: # = (result ok) ref = parser.read_int() - if (call:= self._calls.pop(ref, None)) is None: + if (call := self._calls.pop(ref, None)) is None: logger.warning("Unknown ref number received, discarding") return if call[1] is not None: call[0].set_result(call[1](parser)) else: call[0].set_result(None) - elif reply_type == 37: # % (Error) + elif reply_type == 37: # % (Error) ref = parser.read_int() if (call := self._calls.pop(ref, None)) is None: logger.warning("Unknown ref number received, discarding") @@ -63,12 +64,20 @@ class ClientBase: call[1].set_exception(error_type(ref, error_status)) elif reply_type == 58: - # Async - break + _ = parser.read_int() + async_type_nr = parser.read_int() + if (async_type := ASYNC_TYPES.get(async_type_nr)) is None: + logger.warning(f"Received unknown async-nr: {async_type_nr!r}") + return + message = async_type.parse(parser) + if self._on_event_hook is not None: + await self._on_event_hook(message) + else: logger.warning(fr"Unable to parse packet {packet!r}") - async def send_request(self, call_nr: int, args: bytearray | None, parse_callable: Callable[[MessageParser], Any] | None) -> Any: + async def send_request(self, call_nr: int, args: bytearray | None, + parse_callable: Callable[[MessageParser], Any] | None) -> Any: ref = self._next_ref self._next_ref += 1 message = bytearray(f"{ref} {call_nr}".encode(self.server_encoding)) @@ -112,7 +121,6 @@ class ClientBase: logger.error("OSError") return - self.writer.write(f"A{to_hollerith(self.connection_info)}\n".encode(self.server_encoding)) await self.writer.drain() response = await self.reader.read(1024) @@ -124,8 +132,8 @@ class ClientBase: logger.error("Got non LysKOM response from connection request") return - if len(self._on_connected_hooks) > 0: - asyncio.ensure_future(asyncio.gather(*(f() for f in self._on_connected_hooks))) + if self._on_connected_hook is not None: + asyncio.ensure_future(self._on_connected_hook()) while self.alive: data = await self.reader.read(1024) @@ -133,3 +141,9 @@ class ClientBase: self.alive = False break asyncio.ensure_future(self.on_packet(data)) # This might not be perfect + + def on_async(self, f: Callable[[AsyncMessage], Awaitable[None]]): + self._on_event_hook = f + + def on_connected(self, f: Callable[[], Awaitable[None]]): + self._on_connected_hook = f diff --git a/pyskom/client/kom_types.py b/pyskom/client/kom_types.py index aaa3026b857b2cfa0c55e1a60c25ad86176753cd..fc07369bf403a826dd5636a009f24f03d1e1e653 100644 --- a/pyskom/client/kom_types.py +++ b/pyskom/client/kom_types.py @@ -1,6 +1,5 @@ from enum import Enum -from typing import Any, Self, TYPE_CHECKING, Protocol - +from typing import Any, Self, TYPE_CHECKING, Protocol, ClassVar if TYPE_CHECKING: from .parser import MessageParser @@ -32,6 +31,15 @@ class KomType(Protocol): def parse(cls, parser: 'MessageParser') -> Self: pass +class AsyncMessage(Protocol): + message_pk: ClassVar[int] = -1 + def write(self, writer: 'MessageWriter'): + pass + + @classmethod + def parse(cls, parser: 'MessageParser') -> Self: + pass + # type KomType = tuple[int, Any] type StructMembers = list[tuple[str, KomType]] type EnumMembers = dict[str, Any] diff --git a/pyskom/client/shell/client_shell.py b/pyskom/client/shell/client_shell.py index 63d86ac4596410e17c65a69a21269e86283e2436..11f726a22693476287f3126e9cd8d2076ad30297 100644 --- a/pyskom/client/shell/client_shell.py +++ b/pyskom/client/shell/client_shell.py @@ -14,6 +14,7 @@ from prompt_toolkit.patch_stdout import patch_stdout from ..client import Client from ..kom_error import KomError +from ..kom_types import AsyncMessage from ..types import MiscInfo @@ -55,6 +56,7 @@ class RepPysKomClient: self.command_path_names = ["action", "subaction"] self.parser = self.create_parser() self.client: Client = client + self.client.on_async(self.on_async) super().__init__() async def execute_command(self, parts): @@ -77,6 +79,9 @@ class RepPysKomClient: except KomError as e: print(f"Received KomError: {e!r}") + async def on_async(self, message: AsyncMessage): + print(message) + @command(("help", "?")) async def cmd_help(self, args: Namespace): self.parser.print_help() diff --git a/pyskom/client/types.py b/pyskom/client/types.py index 0c82725ee74d0eac0a42090cc6eddc7cc8a1b737..6572c2de6570108112233e8534d1bf648de79d2f 100644 --- a/pyskom/client/types.py +++ b/pyskom/client/types.py @@ -1,4 +1,4 @@ -# This file was generated at 2025-02-14 13:23:36 by tools.codegen +# This file was generated at 2025-02-14 19:54:32 by tools.codegen # Generated from Protocol A 11.1 # Run `python -m tools.codegen` with project root as CWD to regenerate # For your own sake please do not edit this file as it will be overwritten diff --git a/pyskom/pyskom.py b/pyskom/pyskom.py index 7ba84ec0611a5fc938bff4d35db849279be3594f..cb28476481ea6da470e6ced409abb3b0ba4e4025 100644 --- a/pyskom/pyskom.py +++ b/pyskom/pyskom.py @@ -1,8 +1,11 @@ import asyncio from dataclasses import dataclass +from typing import Any, Iterable from .__about__ import __version__ +from .client.async_types import AsyncLogin, AsyncMessageNr from .client.client import Client +from .client.kom_types import AsyncMessage @dataclass(kw_only=True) @@ -10,14 +13,17 @@ class ConnectionSettings: user_host: tuple[str, str] | None = None use_utc: bool = True # set-connection-time-format [120] client_name_version: tuple[str, str] = ("PysKOM", __version__) + async_messages: Iterable[int] | None = (AsyncMessageNr.LOGIN,) -def get_connection_info(user_host: tuple[str,str] | None): + +def get_connection_info(user_host: tuple[str, str] | None): if user_host is None: import getpass import platform - user_host = [getpass.getuser(),platform.node()] + user_host = [getpass.getuser(), platform.node()] return f"{user_host[0]} % {user_host[1]}" + class PysKom: def __init__(self, username: str, password: str, host: str, port: int = 4894, connection_settings: ConnectionSettings | None = None): @@ -28,28 +34,40 @@ class PysKom: self.connection_settings = connection_settings self.client = Client(host, get_connection_info(connection_settings.user_host), port) - self.client.on_connected(self.on_connected) + self.client.on_connected(self._on_connected) + self.client.on_async(self._on_async) async def run(self): await self.client.connect() async def _set_connection_info(self): - await asyncio.gather( + tasks = [ self.client.set_connection_time_format(self.connection_settings.use_utc), self.client.set_client_version(*self.connection_settings.client_name_version) - ) + ] + if self.connection_settings.async_messages is not None: + tasks.append(self.client.accept_async(list(self.connection_settings.async_messages))) + await asyncio.gather(*tasks) async def _login(self): users = await self.client.lookup_z_name(self.username, True, False) if len(users) != 1: - raise Exception("Unable to find single user") # TODO + raise Exception("Unable to find single user") # TODO user = users[0] - await self.client.login(user.conf_no, self.password, False) # TODO invisible + await self.client.login(user.conf_no, self.password, False) # TODO invisible + await self.on_login(user.name,user.conf_no) + async def _on_async(self, message: Any): + match message.message_nr: + case AsyncMessageNr.LOGIN: + await self.event_on_login(message.pers_no, message.session_no) - async def on_connected(self): + async def _on_connected(self): await self._set_connection_info() await self._login() - async def on_login(self): + async def on_login(self, name: str, pers_no: int): + pass + + async def event_on_login(self, pers_no: int, conf_no: int): pass diff --git a/tools/codegen/ast.py b/tools/codegen/ast.py index b3427fb0aa311f9ae9a3ff3b04bdb03eadfc86a6..c8de77d0bc581ba267c898835fd557d18aff8a0b 100644 --- a/tools/codegen/ast.py +++ b/tools/codegen/ast.py @@ -12,6 +12,12 @@ class KomCall: call_args: list[tuple[str,KomTypeRef]] call_return: KomTypeRef | None +@dataclass +class KomAsyncMessage: + message_nr: int + message_name: str + message_args: list[tuple[str, KomTypeRef]] + class ProtocolSpec: def __init__(self): self.definitions: dict[str, KomType] = {} @@ -19,6 +25,9 @@ class ProtocolSpec: self.calls: list[KomCall] = [] self.aliases = {} self.props = {} + self.async_messages: list[KomAsyncMessage] = [] + # self.async_data = [] + def finalize(self): for i in range(len(self.definitions)): @@ -30,6 +39,7 @@ class ProtocolSpec: self.definitions[d.name] = resolved self.definitions_ordered[i] = resolved self.aliases = {d.name: d.target for d in self.definitions_ordered if isinstance(d, KomAliasType)} + self.async_messages.sort(key=lambda x:x.message_nr) def resolve_aliases(self, name): return self.aliases.get(name) diff --git a/tools/codegen/async_gen.py b/tools/codegen/async_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..752bcea73ad0c7da5a2b1c2e71c5fc854daf13d0 --- /dev/null +++ b/tools/codegen/async_gen.py @@ -0,0 +1,83 @@ + +from .ast import ProtocolSpec, AsyncNode, KomAsyncMessage +from .util import to_python_class_name, to_python_var_name, type_ref_to_python_type, type_ref_to_write_code, \ + type_ref_to_parse_code, codegen_header, get_type_import, to_python_const_name + + +def gen_async_file(spec: ProtocolSpec): + parts = [ + codegen_header("Protocol A " + spec.props.get("PROTOEDITION", "[UNKNOWN]")), + FILE_HEAD + ] + + async_types_lines = [] + async_nr_lines = [] + + used_types = set() + for message in spec.async_messages: + text, part_types = gen_async_type(spec, message) + parts.append(text) + used_types.update(part_types) + async_types_lines.append(f" {message.message_nr}: {to_python_class_name(message.message_name)}") + async_nr_lines.append(f" {to_python_const_name(message.message_name).replace('ASYNC_','')} = {message.message_nr}") + + import_lines = ",\n ".join(used_types) + import_part = f"from .types import (\n {import_lines}\n)" + parts.insert(2, import_part) + + parts.append(f"ASYNC_TYPES = {{\n{',\n'.join(async_types_lines)}\n}}") + parts.append(f"class AsyncMessageNr:\n{'\n'.join(async_nr_lines)}") + + return "\n\n".join(parts) + +def gen_async_type(spec: ProtocolSpec, message: KomAsyncMessage) -> tuple[str, set[str]]: + member_lines = [] + class_name = to_python_class_name(message.message_name) + + used_types = set() + + if len(message.message_args): + parse_members_lines = [] + write_members_lines = [] + for name, member_type in message.message_args: + if (used_type := get_type_import(member_type)) is not None: + used_types.add(used_type) + + var_name = to_python_var_name(name) + member_lines.append(f" {var_name}: {type_ref_to_python_type(member_type)}") + write_members_lines.append(f" {type_ref_to_write_code(spec, member_type, f'self.{var_name}')}") + parse_members_lines.append(f" {type_ref_to_parse_code(spec, member_type)}") + parse_lines = " return cls(\n" + ",\n".join(parse_members_lines) + "\n )" + else: + parse_lines = " return cls()" + write_members_lines = [" pass"] + member_lines.append(f" message_nr: ClassVar[int] = {message.message_nr}") + return ASYNC_TEMPLATE.format( + class_name=class_name, + members="\n".join(member_lines), + parse_lines=parse_lines, + write_lines = "\n".join(write_members_lines) + ), used_types + + +FILE_HEAD = """from dataclasses import dataclass +from enum import Enum +from typing import Union, Self, Any, ClassVar + +from .kom_types import KomType, SimpleType +from .simple_types import KomInt8, KomInt16, KomInt32, KomStr, KomBool,KomFloat +from .parser import MessageParser +from .writer import MessageWriter +""" +ASYNC_TEMPLATE = """@dataclass(slots=True) +class {class_name}: +{members} + + def write(self, writer: MessageWriter): +{write_lines} + + @classmethod + def parse(cls, parser: MessageParser) -> Self: +{parse_lines} +""" + diff --git a/tools/codegen/build_protocol_a.py b/tools/codegen/build_protocol_a.py index 18be0bfda4cff1d2c9774372a9083b144755548f..a32ff4042fb0bd0d9352dd5536edf37989d394d7 100644 --- a/tools/codegen/build_protocol_a.py +++ b/tools/codegen/build_protocol_a.py @@ -1,4 +1,6 @@ -from .ast import DefineNode, parse_define, ProtocolSpec, SetNode, CallNode, KomCall, parse_type_ref +from .ast import DefineNode, parse_define, ProtocolSpec, SetNode, CallNode, KomCall, parse_type_ref, AsyncNode, \ + AsyncDataNode, KomAsyncMessage +from .async_gen import gen_async_file from .call_gen import gen_calls_file from .kom_types import KomStructType from .spec_parser import lexer, parser @@ -35,6 +37,17 @@ def build_protocol_a(text: str): parse_type_ref(returns) if returns is not None else None )) + elif isinstance(item, AsyncNode): + spec.async_messages.append(KomAsyncMessage( + int(item.async_id), + item.name, + [(arg.name, parse_type_ref(arg.value)) for arg in item.data.get_args()] + )) + # elif isinstance(item, AsyncDataNode): + # spec.async_data.append(item) + else: + pass + # print(item) # if isinstance(def_type, KomStructType): # print(generate_struct_class(def_type)) @@ -43,5 +56,9 @@ def build_protocol_a(text: str): with open("pyskom/client/types.py", "w") as f: f.write(gen_types_file(spec)) + with open("pyskom/client/async_types.py", "w") as f: + f.write(gen_async_file(spec)) + with open("pyskom/client/client.py", "w") as f: f.write(gen_calls_file(spec)) + diff --git a/tools/codegen/util.py b/tools/codegen/util.py index c1a1db2d529a521b8b04f3479b5a143639ae580f..d3b860b71972bbd7344790c0e7ea25f0ed94e50e 100644 --- a/tools/codegen/util.py +++ b/tools/codegen/util.py @@ -19,6 +19,18 @@ def to_python_class_name(name: str): def to_python_var_name(name: str): return name.replace("-", "_") +def to_python_const_name(name: str): + return name.upper().replace("-", "_") + +def get_type_import(kom_ref: KomTypeRef) -> str | None: + match kom_ref: + case (KomTypeRefGroup.TYPE, type_name): + return to_python_class_name(type_name) + case (KomTypeRefGroup.ARRAY, (KomTypeRefGroup.TYPE, type_name)): + return to_python_class_name(type_name) + case _: + return None + def type_ref_to_python_type(kom_ref: KomTypeRef): match kom_ref: case (KomTypeRefGroup.SIMPLE, simple_type): @@ -103,7 +115,6 @@ def type_ref_to_parser_callable(spec: ProtocolSpec, kom_ref: KomTypeRef): case (KomTypeRefGroup.ARRAY, (KomTypeRefGroup.SIMPLE, SimpleType.FLOAT)): return "parse_kom_float_array" case _: - print(kom_ref) raise Exception("Unknown type") def type_ref_to_write_code(spec: ProtocolSpec, kom_ref: KomTypeRef, var_name: str):