Freeciv-3.2
Loading...
Searching...
No Matches
generate_packets.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3#
4# Freeciv - Copyright (C) 2003 - Raimar Falke
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2, or (at your option)
8# any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15
16# This script runs under Python 3.5 and up. Please leave it so.
17# It might also run under older versions, but no such guarantees are made.
18
19import re
20import argparse
21import sys
22from pathlib import Path
23from contextlib import contextmanager, ExitStack
24from functools import partial
25from itertools import chain, combinations, takewhile, zip_longest
26from enum import Enum
27from abc import ABC, abstractmethod
28
29try:
30 from functools import cache
31except ImportError:
32 from functools import lru_cache
33 cache = lru_cache(None)
34 del lru_cache
35
36import typing
37T_co = typing.TypeVar("T_co", covariant = True)
38
39
40
41
42def file_path(s: "str | Path") -> Path:
43 """Parse the given path and check basic validity."""
44 path = Path(s)
45
46 if path.is_reserved() or not path.name:
47 raise ValueError("not a valid file path: %r" % s)
48 if path.exists() and not path.is_file():
49 raise ValueError("not a file: %r" % s)
50
51 return path
52
53
55 """Contains configuration info for the script's execution, along with
56 functions closely tied to that configuration"""
57
58 @staticmethod
59 def get_argparser() -> argparse.ArgumentParser:
60 """Construct an argument parser for a packet generation script"""
62 description = "Generate packet-related code from packets.def",
63 add_help = False, # we'll add a help option explicitly
64 )
65
66 # Argument groups
67 # Note the order:
68 # We want the path arguments to show up *first* in the help text
69
71 "Input and output paths",
72 "The following parameters decide which files to read and write."
73 " Omitting an output path will not generate that file.",
74 )
75
77 "Script configuration",
78 "The following parameters change how the script operates.",
79 )
80
82 "Output configuration",
83 "The following parameters change the amount of output.",
84 )
85
87 "Protocol configuration",
88 "The following parameters CHANGE the protocol."
89 " You have been warned.",
90 )
91
92 # Individual arguments
93 # Note the order:
94 # We want the path arguments to show up *last* in the usage summary
95
96 script.add_argument("-h", "--help", action = "help",
97 help = "show this help message and exit")
98
99 script.add_argument("-v", "--verbose", action = "store_true",
100 help = "enable log messages during code generation")
101
102 # When enabled: Only overwrite existing output files when they
103 # actually changed. This prevents make from rebuilding all dependents
104 # in cases where that wouldn't even be necessary.
105 script.add_argument("--lazy-overwrite", action = "store_true",
106 help = "only overwrite output files when their"
107 " contents actually changed")
108
109 output.add_argument("-s", "--gen-stats", action = "store_true",
110 help = "generate code reporting packet usage"
111 " statistics; call delta_stats_report to get these")
112
114 logs.add_argument("-l", "--log-macro", default = "log_packet_detailed",
115 help = "use the given macro for generated log calls")
116 logs.add_argument("-L", "--no-logs", dest = "log_macro",
117 action = "store_const", const = None,
118 help = "disable generating log calls")
119
120 protocol.add_argument("-B", "--no-fold-bool",
121 dest = "fold_bool", action = "store_false",
122 help = "explicitly encode boolean values in the"
123 " packet body, rather than folding them into the"
124 " packet header")
125
126 output_path_args = (
127 # (dest, option, canonical path)
128 ("common_header_path", "--common-h", "common/packets_gen.h"),
129 ("common_impl_path", "--common-c", "common/packets_gen.c"),
130 ("client_header_path", "--client-h", "client/packhand_gen.h"),
131 ("client_impl_path", "--client-c", "client/packhand_gen.c"),
132 ("server_header_path", "--server-h", "server/hand_gen.h"),
133 ("server_impl_path", "--server-c", "server/hand_gen.c"),
134 )
135
136 for dest, option, canonical in output_path_args:
137 paths.add_argument(option, dest = dest, type = file_path,
138 help = "output path for %s" % canonical)
139
140 paths.add_argument("def_paths", metavar = "def_path",
141 nargs = "+", type = file_path,
142 help = "paths to your packets.def file")
143
144 return parser
145
146 def __init__(self, args: "typing.Sequence[str] | None" = None):
147 # type hints and docstrings for fields
148 # FIXME: Once we can use Python 3.6 features, turn these into
149 # (class-level) variable annotations
151 self.def_paths = [file_path("")]
152 """Paths to definition files, in load order"""
153 optional_path = (file_path(""), None)[int()]
154 self.common_header_path = optional_path
155 """Output path for the common header, or None if that should not
156 be generated"""
157 self.common_impl_path = optional_path
158 """Output path for the common implementation, or None if that
159 should not be generated"""
160 self.server_header_path = optional_path
161 """Output path for the server header, or None if that should not
162 be generated"""
163 self.server_impl_path = optional_path
164 """Output path for the server implementation, or None if that
165 should not be generated"""
166 self.client_header_path = optional_path
167 """Output path for the client header, or None if that should not
168 be generated"""
169 self.client_impl_path = optional_path
170 """Output path for the client implementation, or None if that
171 should not be generated"""
172
173 self.verbose = False
174 """Whether to enable verbose logging"""
175 self.lazy_overwrite = False
176 """Whether to lazily overwrite output files"""
177
178 self.gen_stats = False
179 """Whether to generate delta stats code"""
180 self.log_macro = str() or None
181 """The macro used for log calls, or None if no log calls should
182 be generated"""
183
184 self.fold_bool = True
185 """Whether to fold boolean fields into the packet header"""
186
187 __class__.get_argparser().parse_args(args, namespace = self)
188
189 def log_verbose(self, *args):
190 """Print the given arguments iff verbose logging is enabled"""
191 if self.verbose:
192 print(*args)
193
194 @property
195 def _root_path(self) -> "Path | None":
196 """Root Freeciv path, if we can find it."""
197 path = Path(__file__).absolute()
198 root = path.parent.parent
199 if path != root / "common" / "generate_packets.py":
200 self.log_verbose("Warning: couldn't find Freeciv root path")
201 return None
202 return root
203
204 def _relative_path(self, path: Path) -> Path:
205 """Find the relative path from the Freeciv root to the given path.
206 Return the path unmodified if it's outside the Freeciv root, or if
207 the Freeciv root could not be found."""
208 root = self._root_path
209 if root is not None:
210 try:
211 return path.absolute().relative_to(root)
212 except ValueError:
213 self.log_verbose("Warning: path %s outside of Freeciv root" % path)
214 return path
215
216 @property
217 def _script_path(self) -> Path:
218 """Relative path of the executed script. Under normal circumstances,
219 this will be common/generate_packets.py, but it may differ when this
220 module is imported from another script."""
221 return self._relative_path(Path(sys.argv[0]))
222
223 def _write_disclaimer(self, f: typing.TextIO):
224 f.write("""\
225 /**************************************************************************
226 * THIS FILE WAS GENERATED *
227 * Script: %-63s *
228""" % self._script_path)
229
230 for path in self.def_paths:
231 f.write("""\
232 * Input: %-63s *
233""" % self._relative_path(path))
234
235 f.write("""\
236 * DO NOT CHANGE THIS FILE *
237 **************************************************************************/
238
239""")
240
241 @contextmanager
242 def _wrap_header(self, file: typing.TextIO, header_name: str) -> typing.Iterator[None]:
243 """Add multiple inclusion protection to the given file"""
244 name = "FC__%s_H" % header_name.upper()
245 file.write("""\
246#ifndef {name}
247#define {name}
248
249""".format(name = name))
250
251 yield
252
253 file.write("""\
254
255#endif /* {name} */
256""".format(name = name))
257
258 @contextmanager
259 def _wrap_cplusplus(self, file: typing.TextIO) -> typing.Iterator[None]:
260 """Add code for `extern "C" {}` wrapping"""
261 file.write("""\
262#ifdef __cplusplus
263extern "C" {
264#endif /* __cplusplus */
265
266""")
267 yield
268 file.write("""\
269
270#ifdef __cplusplus
271}
272#endif /* __cplusplus */
273""")
274
275 @contextmanager
276 def open_write(self, path: "str | Path", wrap_header: "str | None" = None, cplusplus: bool = True) -> typing.Iterator[typing.TextIO]:
277 """Open a file for writing and write disclaimer.
278
279 If enabled, lazily overwrites the given file.
280 If wrap_header is given, add multiple inclusion protection; if
281 cplusplus is also given (default), also add code for `extern "C"`
282 wrapping."""
283 path = Path(path) # no-op if path is already a Path object
284 self.log_verbose("writing %s" % path)
285
286 with ExitStack() as stack:
287 if self.lazy_overwrite:
288 file = stack.enter_context(self.lazy_overwrite_open(path))
289 else:
290 file = stack.enter_context(path.open("w"))
291
292 self._write_disclaimer(file)
293
294 if wrap_header is not None:
295 stack.enter_context(self._wrap_header(file, wrap_header))
296 if cplusplus:
297 stack.enter_context(self._wrap_cplusplus(file))
298 yield file
299 self.log_verbose("done writing %s" % path)
300
301 @contextmanager
302 def lazy_overwrite_open(self, path: "str | Path", suffix: str = ".tmp") -> typing.Iterator[typing.TextIO]:
303 """Open a file for writing, but only actually overwrite it if the new
304 content differs from the old content.
305
306 This creates a temporary file by appending the given suffix to the given
307 file path. In the event of an error, this temporary file might remain in
308 the target file's directory."""
309
310 path = Path(path)
311 tmp_path = path.with_name(path.name + suffix)
312
313 # if tmp_path already exists, assume it's left over from a previous,
314 # failed run and can be overwritten without trouble
315 self.log_verbose("lazy: using %s" % tmp_path)
316 with tmp_path.open("w") as file:
317 yield file
318
319 if path.exists() and files_equal(tmp_path, path):
320 self.log_verbose("lazy: no change, deleting...")
321 tmp_path.unlink()
322 else:
323 self.log_verbose("lazy: content changed, replacing...")
324 tmp_path.replace(path)
325
326
327################### General helper functions and classes ###################
328
329def files_equal(path_a: "str | Path", path_b: "str | Path") -> bool:
330 """Return whether the contents of two text files are identical"""
331 with Path(path_a).open() as file_a, Path(path_b).open() as file_b:
332 return all(a == b for a, b in zip_longest(file_a, file_b))
333
334# Taken from https://docs.python.org/3.4/library/itertools.html#itertools-recipes
335def powerset(iterable: typing.Iterable[T_co]) -> "typing.Iterator[tuple[T_co, ...]]":
336 "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
337 s = list(iterable)
338 return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
339
340INSERT_PREFIX_PATTERN = re.compile(r"^(?!#|$)", re.MULTILINE)
341"""Matches the beginning of nonempty lines that are not preprocessor
342directives, i.e. don't start with a #"""
343
344def prefix(prefix: str, text: str) -> str:
345 """Prepend prefix to every line of text, except blank lines and those
346 starting with #"""
347 return INSERT_PREFIX_PATTERN.sub(prefix, text)
348
349
350class Location:
351 """Roughly represents a location in memory for the generated code;
352 outside of recursive field types like arrays, this will usually just be
353 a field of a packet, but it serves to concisely handle the recursion."""
354
355 _INDICES = "ijk"
356
357 def __init__(self, name: str, location: "str | None" = None, depth: int = 0):
358 self.name = name
359 """The name associated with this location; used in log messages."""
360 if location is None:
361 self.location = name
362 """The actual location as used in code"""
363 else:
364 self.location = location
365 self.depth = depth
366 """The array nesting depth of this location; used to determine index
367 variable names."""
368
369 def deeper(self, new_location: str) -> "Location":
370 """Return the given string as a new Location with the same name as
371 self and incremented depth"""
372 return type(self)(self.name, new_location, self.depth + 1)
373
374 @property
375 def index(self) -> str:
376 """The index name for the current depth"""
377 if self.depth > len(self._INDICES):
378 return self._INDICES[0] + str(self.depth) # i3, i4, i5...
379 return self._INDICES[self.depth]
380
381 @property
382 def sub(self) -> "Location":
383 """A location one level deeper with the current index subscript
384 added to the end.
385
386 `field` ~> `field[i]` ~> `field[i][j]` etc."""
387 return self.deeper("{self}[{self.index}]".format(self = self))
388
389 def __str__(self) -> str:
390 return self.location
391
392 def __repr__(self) -> str:
393 return "{self.__class__.__name__}({self.name!r}, {self.location!r}, {self.depth!r})".format(self = self)
394
395
396#################### Components of a packets definition ####################
397
398class FieldFlags:
399 """Information about flags of a given Field. Multiple Field objects can
400 share one FieldFlags instance, e.g. when defined on the same line."""
401
402 ADD_CAP_PATTERN = re.compile(r"^add-cap\‍(([^()]+)\‍)$")
403 """Matches an add-cap flag (optional capability)"""
404
405 REMOVE_CAP_PATTERN = re.compile(r"^remove-cap\‍(([^()]+)\‍)$")
406 """Matches a remove-cap flag (optional capability)"""
407
408 @classmethod
409 @cache
410 def parse(cls, flags_text: str) -> "FieldFlags":
411 """Parse a FieldFlags object from a comma-separated flag line"""
412 return cls(
413 stripped
414 for flag in flags_text.split(",")
415 for stripped in (flag.strip(),)
416 if stripped
417 )
418
419 is_key = False
420 """Whether the field is a key field"""
421
422 diff = False
423 """Whether the field should be deep-diffed for transmission"""
424
425 def __init__(self, flag_texts: typing.Iterable[str]):
426 self.add_caps = set()
427 """The capabilities required to enable the field"""
428
429 self.remove_caps = set()
430 """The capabilities that disable the field"""
431
432 for flag in flag_texts:
433 if flag == "key":
434 self.is_key = True
435 continue
436 if flag == "diff":
437 self.diff = True
438 continue
439 mo = __class__.ADD_CAP_PATTERN.fullmatch(flag)
440 if mo is not None:
441 self.add_caps.add(mo.group(1))
442 continue
443 mo = __class__.REMOVE_CAP_PATTERN.fullmatch(flag)
444 if mo is not None:
445 self.remove_caps.add(mo.group(1))
446 continue
447 raise ValueError("unrecognized flag in field declaration: %s" % flag)
448
449 contradictions = self.add_caps & self.remove_caps
450 if contradictions:
451 raise ValueError("cannot have same capabilities as both add-cap and remove-cap: %s" % ", ".join(contradictions))
452
453
454class SizeInfo:
455 """Information about size along one dimension of an array or other sized
456 field type. Contains both the declared / maximum size, and the actual
457 used size (if different)."""
458
459 ARRAY_SIZE_PATTERN = re.compile(r"^([^:]+)(?:\:([^:]+))?$")
460 """Matches an array size declaration (without the brackets)
461
462 Groups:
463 - the declared / maximum size
464 - the field name for the actual size (optional)"""
465
466 @classmethod
467 @cache
468 def parse(cls, size_text) -> "SizeInfo":
469 """Parse the given array size text (without brackets)"""
470 mo = cls.ARRAY_SIZE_PATTERN.fullmatch(size_text)
471 if mo is None:
472 raise ValueError("invalid array size declaration: [%s]" % size_text)
473 return cls(*mo.groups())
474
475 def __init__(self, declared: str, actual: "str | None"):
476 self.declared = declared
477 """Maximum size; used in declarations"""
478 self._actual = actual
479 """Name of the field to use for the actual size, or None if the
480 entire array should always be transmitted."""
481
482 @property
483 def constant(self) -> bool:
484 """Whether the actual size doesn't change"""
485 return self._actual is None
486
487 def actual_for(self, packet: str) -> str:
488 """Return a code expression representing the actual size for the
489 given packet"""
490 if self.constant:
491 return self.declared
492 return "%s->%s" % (packet, self._actual)
493
494 @property
495 def real(self) -> str:
496 """The number of elements to transmit. Either the same as the
497 declared size, or a field of `*real_packet`."""
498 return self.actual_for("real_packet")
499
500 @property
501 def old(self) -> str:
502 """The number of elements transmitted last time. Either the same as
503 the declared size, or a field of `*old`."""
504 return self.actual_for("old")
505
506 def size_check_get(self, field_name: str) -> str:
507 """Generate a code snippet checking whether the received size is in
508 range when receiving a packet."""
509 if self.constant:
510 return ""
511 return """\
512if ({self.real} > {self.declared}) {{
513 RECEIVE_PACKET_FIELD_ERROR({field_name}, ": truncation array");
514}}
515""".format(self = self, field_name = field_name)
516
517 def size_check_index(self, field_name: str) -> str:
518 """Generate a code snippet asserting that indices can be correctly
519 transmitted for array-diff."""
520 if self.constant:
521 return """\
522FC_STATIC_ASSERT({self.declared} <= MAX_UINT16, packet_array_too_long_{field_name});
523""".format(self = self, field_name = field_name)
524 else:
525 return """\
526fc_assert({self.real} <= MAX_UINT16);
527""".format(self = self)
528
529 def index_put(self, index: str) -> str:
530 """Generate a code snippet writing the given value to the network
531 output, encoded as the appropriate index type"""
532 if self.constant:
533 return """\
534#if {self.declared} <= MAX_UINT8
535e |= DIO_PUT(uint8, &dout, &field_addr, {index});
536#else
537e |= DIO_PUT(uint16, &dout, &field_addr, {index});
538#endif
539""".format(self = self, index = index)
540 else:
541 return """\
542if ({self.real} <= MAX_UINT8) {{
543 e |= DIO_PUT(uint8, &dout, &field_addr, {index});
544}} else {{
545 e |= DIO_PUT(uint16, &dout, &field_addr, {index});
546}}
547""".format(self = self, index = index)
548
549 def index_get(self, location: Location) -> str:
550 """Generate a code snippet reading the next index from the
551 network input decoded as the correct type"""
552 if self.constant:
553 return """\
554#if {self.declared} <= MAX_UINT8
555if (!DIO_GET(uint8, &din, &field_addr, &{location.index})) {{
556#else
557if (!DIO_GET(uint16, &din, &field_addr, &{location.index})) {{
558#endif
559 RECEIVE_PACKET_FIELD_ERROR({location.name});
560}}
561""".format(self = self, location = location)
562 else:
563 return """\
564if (({self.real} <= MAX_UINT8)
565 ? !DIO_GET(uint8, &din, &field_addr, &{location.index})
566 : !DIO_GET(uint16, &din, &field_addr, &{location.index})) {{
567 RECEIVE_PACKET_FIELD_ERROR({location.name});
568}}
569""".format(self = self, location = location)
570
571 def __str__(self) -> str:
572 if self._actual is None:
573 return self.declared
574 return "%s:%s" % (self.declared, self._actual)
575
576 def __eq__(self, other) -> bool:
577 if not isinstance(other, __class__):
578 return NotImplemented
579 return (self.declared == other.declared
580 and self._actual == other._actual)
581
582 def __hash__(self) -> int:
583 return hash((__class__, self.declared, self._actual))
584
585
586class RawFieldType(ABC):
587 """Abstract base class (ABC) for classes representing types defined in a
588 packets definition file. These types may require the addition of a size
589 in order to be usable; see the array() method and the FieldType class."""
590
591 @abstractmethod
592 def array(self, size: SizeInfo) -> "FieldType":
593 """Add an array size to this field type, either to make a type which
594 needs a size fully usable, or to make an array type with self as
595 the element type."""
596 raise NotImplementedError
597
598 @abstractmethod
599 def __str__(self) -> str:
600 return super().__str__()
601
602 def __repr__(self) -> str:
603 return "<{self.__class__.__name__} {self}>".format(self = self)
604
605
606FieldTypeConstructor = typing.Callable[[str, str], RawFieldType]
607
608class TypeRegistry:
609 """Determines what Python class to use for field types based on their
610 dataio type and public type."""
611
612 TYPE_INFO_PATTERN = re.compile(r"^([^()]*)\‍(([^()]*)\‍)$")
613 """Matches a field type.
614
615 Groups:
616 - dataio type
617 - public type (aka struct type)"""
618
619 def __init__(self, fallback: FieldTypeConstructor):
620 # FIXME: Once we can use Python 3.6 features, turn these into
621 # (class-level) variable annotations
622 self.dataio_types = {} # type: dict[str, FieldTypeConstructor]
623 """Dictionary mapping dataio types to the constructor used for
624 field types with that dataio type.
625
626 This is the primary factor deciding what constructor to use for a
627 given field type."""
628 self.dataio_patterns = {} # type: dict[typing.Pattern[str], FieldTypeConstructor]
629 """Dictionary mapping RegEx patterns to the constructor used for
630 field types whose dataio type matches that pattern.
631
632 Matches are cached in self.dataio_types."""
633 self.public_types = {} # type: dict[str, FieldTypeConstructor]
634 """Like self.dataio_types, but for public types.
635
636 This is only checked if there are no matches in self.dataio_types
637 and self.dataio_patterns."""
638 self.public_patterns = {} # type: dict[typing.Pattern[str], FieldTypeConstructor]
639 """Like self.dataio_patterns, but for public types.
640
641 Matches are cached in self.public_types."""
642 self.fallback = fallback
643 """Fallback constructor used when there are no matches for either
644 dataio type or public type."""
645
646 def parse(self, type_text: str) -> RawFieldType:
647 """Parse a single field type"""
648 mo = __class__.TYPE_INFO_PATTERN.fullmatch(type_text)
649 if mo is None:
650 raise ValueError("malformed type or undefined alias: %r" % type_text)
651 return self(*mo.groups())
652
653 def __call__(self, dataio_type: str, public_type: str) -> RawFieldType:
654 try:
655 ctor = self.dataio_types[dataio_type]
656 except KeyError:
657 pass
658 else:
659 return ctor(dataio_type, public_type)
660
661 for pat, ctor in self.dataio_patterns.items():
662 mo = pat.fullmatch(dataio_type)
663 if mo is not None:
664 self.dataio_types[dataio_type] = ctor
665 return ctor(dataio_type, public_type)
666
667 self.dataio_types[dataio_type] = self._by_public
668 return self._by_public(dataio_type, public_type)
669
670 def _by_public(self, dataio_type: str, public_type: str) -> RawFieldType:
671 try:
672 ctor = self.public_types[public_type]
673 except KeyError:
674 pass
675 else:
676 return ctor(dataio_type, public_type)
677
678 for pat, ctor in self.public_patterns.items():
679 mo = pat.fullmatch(public_type)
680 if mo is not None:
681 self.public_types[public_type] = ctor
682 return ctor(dataio_type, public_type)
683
684 self.public_types[public_type] = self.fallback
685 return self.fallback(dataio_type, public_type)
686
687
688class NeedSizeType(RawFieldType):
689 """Helper class for field types that require a size to be usable."""
690
691 def __init__(self, dataio_type: str, public_type: str, cls: typing.Callable[[str, str, SizeInfo], "FieldType"]):
692 self.dataio_type = dataio_type
693 """The dataio type passed to self.cls"""
694 self.public_type = public_type
695 """The public type passed to self.cls"""
696 self.cls = cls
697 """The field type constructed when adding a size to this type"""
698
699 def array(self, size: SizeInfo) -> "FieldType":
700 """Add an array size to make a usable type."""
701 return self.cls(self.dataio_type, self.public_type, size)
702
703 def __str__(self) -> str:
704 return "{self.dataio_type}({self.public_type})".format(self = self)
705
706
707class FieldType(RawFieldType):
708 """Abstract base class (ABC) for classes representing type information
709 usable for fields of a packet"""
710
711 foldable = False
712 """Whether a field of this type can be folded into the packet header"""
713
714 @cache
715 def array(self, size: SizeInfo) -> "FieldType":
716 """Construct a FieldType for an array with element type self and the
717 given size"""
718 return ArrayType(self, size)
719
720 @abstractmethod
721 def get_code_declaration(self, location: Location) -> str:
722 """Generate a code snippet declaring a field with this type in a
723 packet struct."""
724 raise NotImplementedError
725
726 @abstractmethod
727 def get_code_handle_param(self, location: Location) -> str:
728 """Generate a code fragment declaring a parameter with this type for
729 a handle function.
730
731 See also self.get_code_handle_arg()"""
732 raise NotImplementedError
733
734 def get_code_handle_arg(self, location: Location) -> str:
735 """Generate a code fragment passing an argument with this type to a
736 handle function.
737
738 See also self.get_code_handle_param()"""
739 return str(location)
740
741 @abstractmethod
742 def get_code_fill(self, location: Location) -> str:
743 """Generate a code snippet moving a value of this type from dsend
744 arguments into a packet struct."""
745 raise NotImplementedError
746
747 @abstractmethod
748 def get_code_hash(self, location: Location) -> str:
749 """Generate a code snippet factoring a field of this type into a
750 hash computation's `result`."""
751 raise NotImplementedError
752
753 @abstractmethod
754 def get_code_cmp(self, location: Location) -> str:
755 """Generate a code snippet comparing a field of this type between
756 the `old` and `real_packet` and setting `differ` accordingly."""
757 raise NotImplementedError
758
759 @abstractmethod
760 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
761 """Generate a code snippet writing a field of this type to the
762 dataio stream."""
763 raise NotImplementedError
764
765 @abstractmethod
766 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
767 """Generate a code snippet reading a field of this type from the
768 dataio stream."""
769 raise NotImplementedError
770
771 def _compat_keys(self, location: Location):
772 """Internal helper function. Yield keys to compare for
773 type compatibility. See is_type_compatible()"""
774 yield self.get_code_declaration(location)
775 yield self.get_code_handle_param(location)
776 yield self.get_code_handle_arg(location)
777 yield self.get_code_fill(location)
778
779 def is_type_compatible(self, other: "FieldType") -> bool:
780 """Determine whether two field types can be used interchangeably as
781 part of the packet struct, i.e. differ in dataio transmission only"""
782 if other is self:
783 return True
784 loc = Location("compat_test_field_name")
785 return all(
786 a == b
787 for a, b in zip_longest(
788 self._compat_keys(loc),
789 other._compat_keys(loc),
790 )
791 )
792
793
794class BasicType(FieldType):
795 """Type information for a field without any specialized treatment"""
796
797 def __init__(self, dataio_type: str, public_type: str):
798 self.dataio_type = dataio_type
799 """How fields of this type are transmitted over network"""
800 self.public_type = public_type
801 """How fields of this type are represented in C code"""
802
803 def get_code_declaration(self, location: Location) -> str:
804 return """\
805{self.public_type} {location};
806""".format(self = self, location = location)
807
808 def get_code_handle_param(self, location: Location) -> str:
809 return "{self.public_type} {location}".format(self = self, location = location)
810
811 def get_code_fill(self, location: Location) -> str:
812 return """\
813real_packet->{location} = {location};
814""".format(location = location)
815
816 def get_code_hash(self, location: Location) -> str:
817 raise ValueError("hash not supported for type %s in field %s" % (self, location.name))
818
819 def get_code_cmp(self, location: Location) -> str:
820 return """\
821differ = (old->{location} != real_packet->{location});
822""".format(self = self, location = location)
823
824 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
825 return """\
826e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, real_packet->{location});
827""".format(self = self, location = location)
828
829 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
830 return """\
831if (!DIO_GET({self.dataio_type}, &din, &field_addr, &real_packet->{location})) {{
832 RECEIVE_PACKET_FIELD_ERROR({location.name});
833}}
834""".format(self = self, location = location)
835
836 def __str__(self) -> str:
837 return "{self.dataio_type}({self.public_type})".format(self = self)
838
839DEFAULT_REGISTRY = TypeRegistry(BasicType)
840"""The default type registry used by a PacketsDefinition when no other
841registry is given."""
842
843
844class IntType(BasicType):
845 """Type information for an integer field"""
846
847 TYPE_PATTERN = re.compile(r"^[su]int\d+$")
848 """Matches an int dataio type"""
849
850 @typing.overload
851 def __init__(self, dataio_info: str, public_type: str): ...
852 @typing.overload
853 def __init__(self, dataio_info: "re.Match[str]", public_type: str): ...
854 def __init__(self, dataio_info: "str | re.Match[str]", public_type: str):
855 if isinstance(dataio_info, str):
856 mo = self.TYPE_PATTERN.fullmatch(dataio_info)
857 if mo is None:
858 raise ValueError("not a valid int type")
859 dataio_info = mo
860 dataio_type = dataio_info.group(0)
861
862 super().__init__(dataio_type, public_type)
863
864 def get_code_hash(self, location: Location) -> str:
865 return """\
866result += key->%s;
867""" % location
868
869 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
870 if self.public_type in ("int", "bool"):
871 # read directly
872 return super().get_code_get(location, deep_diff)
873 # read indirectly to make sure coercions between different integer
874 # types happen correctly
875 return """\
876{{
877 int readin;
878
879 if (!DIO_GET({self.dataio_type}, &din, &field_addr, &readin)) {{
880 RECEIVE_PACKET_FIELD_ERROR({location.name});
881 }}
882 real_packet->{location} = readin;
883}}
884""".format(self = self, location = location)
885
886DEFAULT_REGISTRY.dataio_patterns[IntType.TYPE_PATTERN] = IntType
887
888
889class BoolType(IntType):
890 """Type information for a boolean field"""
891
892 TYPE_PATTERN = re.compile(r"^bool\d*$")
893 """Matches a bool dataio type"""
894
895 foldable = True
896
897 @typing.overload
898 def __init__(self, dataio_info: str, public_type: str): ...
899 @typing.overload
900 def __init__(self, dataio_info: "re.Match[str]", public_type: str): ...
901 def __init__(self, dataio_info: "str | re.Match[str]", public_type: str):
902 if isinstance(dataio_info, str):
903 mo = self.TYPE_PATTERN.fullmatch(dataio_info)
904 if mo is None:
905 raise ValueError("not a valid bool type")
906 dataio_info = mo
907
908 if public_type != "bool":
909 raise ValueError("bool dataio type with non-bool public type: %r" % public_type)
910
911 super().__init__(dataio_info, public_type)
912
913DEFAULT_REGISTRY.dataio_patterns[BoolType.TYPE_PATTERN] = BoolType
914
915
916class FloatType(BasicType):
917 """Type information for a float field"""
918
919 TYPE_PATTERN = re.compile(r"^([su]float)(\d+)?$")
920 """Matches a float dataio type
921
922 Note: Will also match float types without a float factor to avoid
923 falling back to the default; in this case, the second capturing group
924 will not match.
925
926 Groups:
927 - non-numeric dataio type
928 - numeric float factor"""
929
930 @typing.overload
931 def __init__(self, dataio_info: str, public_type: str): ...
932 @typing.overload
933 def __init__(self, dataio_info: "re.Match[str]", public_type: str): ...
934 def __init__(self, dataio_info: "str | re.Match[str]", public_type: str):
935 if isinstance(dataio_info, str):
936 mo = self.TYPE_PATTERN.fullmatch(dataio_info)
937 if mo is None:
938 raise ValueError("not a valid float type")
939 dataio_info = mo
940 dataio_type, float_factor = dataio_info.groups()
941 if float_factor is None:
942 raise ValueError("float type without float factor: %r" % dataio_info.string)
943
944 if public_type != "float":
945 raise ValueError("float dataio type with non-float public type: %r" % public_type)
946
947 super().__init__(dataio_type, public_type)
948 self.float_factor = int(float_factor)
949 """Granularity (fixed-point factor) used to transmit this type in an
950 integer"""
951
952 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
953 return """\
954e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, real_packet->{location}, {self.float_factor:d});
955""".format(self = self, location = location)
956
957 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
958 return """\
959if (!DIO_GET({self.dataio_type}, &din, &field_addr, &real_packet->{location}, {self.float_factor:d})) {{
960 RECEIVE_PACKET_FIELD_ERROR({location.name});
961}}
962""".format(self = self, location = location)
963
964 def __str__(self) -> str:
965 return "{self.dataio_type}{self.float_factor:d}({self.public_type})".format(self = self)
966
967DEFAULT_REGISTRY.dataio_patterns[FloatType.TYPE_PATTERN] = FloatType
968
969
970class BitvectorType(BasicType):
971 """Type information for a bitvector field"""
972
973 def __init__(self, dataio_type: str, public_type: str):
974 if dataio_type != "bitvector":
975 raise ValueError("not a valid bitvector type")
976
977 super().__init__(dataio_type, public_type)
978
979 def get_code_cmp(self, location: Location) -> str:
980 return """\
981differ = !BV_ARE_EQUAL(old->{location}, real_packet->{location});
982""".format(self = self, location = location)
983
984 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
985 return """\
986e |= DIO_BV_PUT(&dout, &field_addr, packet->{location});
987""".format(location = location)
988
989 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
990 return """\
991if (!DIO_BV_GET(&din, &field_addr, real_packet->{location})) {{
992 RECEIVE_PACKET_FIELD_ERROR({location.name});
993}}
994""".format(self = self, location = location)
995
996DEFAULT_REGISTRY.dataio_types["bitvector"] = BitvectorType
997
998
999class StructType(BasicType):
1000 """Type information for a field of some general struct type"""
1001
1002 TYPE_PATTERN = re.compile(r"^struct \w+$")
1003 """Matches a struct public type"""
1004
1005 @typing.overload
1006 def __init__(self, dataio_type: str, public_info: str): ...
1007 @typing.overload
1008 def __init__(self, dataio_type: str, public_info: "re.Match[str]"): ...
1009 def __init__(self, dataio_type: str, public_info: "str | re.Match[str]"):
1010 if isinstance(public_info, str):
1011 mo = self.TYPE_PATTERN.fullmatch(public_info)
1012 if mo is None:
1013 raise ValueError("not a valid struct type")
1014 public_info = mo
1015 public_type = public_info.group(0)
1016
1017 super().__init__(dataio_type, public_type)
1018
1019 def get_code_handle_param(self, location: Location) -> str:
1020 if not location.depth:
1021 # top level: pass by-reference
1022 return "const " + super().get_code_handle_param(location.deeper("*%s" % location))
1023 return super().get_code_handle_param(location)
1024
1025 def get_code_handle_arg(self, location: Location) -> str:
1026 if not location.depth:
1027 # top level: pass by-reference
1028 return super().get_code_handle_arg(location.deeper("&%s" % location))
1029 return super().get_code_handle_arg(location)
1030
1031 def get_code_cmp(self, location: Location) -> str:
1032 return """\
1033differ = !are_{self.dataio_type}s_equal(&old->{location}, &real_packet->{location});
1034""".format(self = self, location = location)
1035
1036 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
1037 return """\
1038e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &real_packet->{location});
1039""".format(self = self, location = location)
1040
1041DEFAULT_REGISTRY.public_patterns[StructType.TYPE_PATTERN] = StructType
1042
1043
1044class CmParameterType(StructType):
1045 """Type information for a worklist field"""
1046
1047 def __init__(self, dataio_type: str, public_type: str):
1048 if dataio_type != "cm_parameter":
1049 raise ValueError("not a valid cm_parameter type")
1050
1051 if public_type != "struct cm_parameter":
1052 raise ValueError("cm_parameter dataio type with non-cm_parameter public type: %r" % public_type)
1053
1054 super().__init__(dataio_type, public_type)
1055
1056 def get_code_cmp(self, location: Location) -> str:
1057 return """\
1058differ = !cm_are_parameter_equal(&old->{location}, &real_packet->{location});
1059""".format(self = self, location = location)
1060
1061DEFAULT_REGISTRY.dataio_types["cm_parameter"] = CmParameterType
1062
1063
1064class WorklistType(StructType):
1065 """Type information for a worklist field"""
1066
1067 def __init__(self, dataio_type: str, public_type: str):
1068 if dataio_type != "worklist":
1069 raise ValueError("not a valid worklist type")
1070
1071 if public_type != "struct worklist":
1072 raise ValueError("worklist dataio type with non-worklist public type: %r" % public_type)
1073
1074 super().__init__(dataio_type, public_type)
1075
1076 def get_code_fill(self, location: Location) -> str:
1077 return """\
1078worklist_copy(&real_packet->{location}, {location});
1079""".format(location = location)
1080
1081DEFAULT_REGISTRY.dataio_types["worklist"] = WorklistType
1082
1083
1084class SizedType(BasicType):
1085 """Abstract base class (ABC) for field types that include a size"""
1086
1087 def __init__(self, dataio_type: str, public_type: str, size: SizeInfo):
1088 super().__init__(dataio_type, public_type)
1089 self.size = size
1090 """Size info (maximum and actual) of this type"""
1091
1092 def get_code_declaration(self, location: Location) -> str:
1093 return super().get_code_declaration(
1094 location.deeper("%s[%s]" % (location, self.size.declared))
1095 )
1096
1097 def get_code_handle_param(self, location: Location) -> str:
1098 # add "const" if top level
1099 pre = "" if location.depth else "const "
1100 return pre + super().get_code_handle_param(location.deeper("*%s" % location))
1101
1102 @abstractmethod
1103 def get_code_fill(self, location: Location) -> str:
1104 return super().get_code_fill(location)
1105
1106 def __str__(self) -> str:
1107 return "%s[%s]" % (super().__str__(), self.size)
1108
1109
1110class StringType(SizedType):
1111 """Type information for a string field"""
1112
1113 def __init__(self, dataio_type: str, public_type: str, size: SizeInfo):
1114 if dataio_type not in ("string", "estring"):
1115 raise ValueError("not a valid string type")
1116
1117 if public_type != "char":
1118 raise ValueError("string dataio type with non-char public type: %r" % public_type)
1119
1120 super().__init__(dataio_type, public_type, size)
1121
1122 def get_code_fill(self, location: Location) -> str:
1123 return """\
1124sz_strlcpy(real_packet->{location}, {location});
1125""".format(location = location)
1126
1127 def get_code_cmp(self, location: Location) -> str:
1128 return """\
1129differ = (strcmp(old->{location}, real_packet->{location}) != 0);
1130""".format(self = self, location = location)
1131
1132 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
1133 return """\
1134if (!DIO_GET({self.dataio_type}, &din, &field_addr, real_packet->{location}, sizeof(real_packet->{location}))) {{
1135 RECEIVE_PACKET_FIELD_ERROR({location.name});
1136}}
1137""".format(self = self, location = location)
1138
1139DEFAULT_REGISTRY.dataio_types["string"] = DEFAULT_REGISTRY.dataio_types["estring"] = partial(NeedSizeType, cls = StringType)
1140
1141
1142class MemoryType(SizedType):
1143 """Type information for a memory field"""
1144
1145 def __init__(self, dataio_type: str, public_type: str, size: SizeInfo):
1146 if dataio_type != "memory":
1147 raise ValueError("not a valid memory type")
1148
1149 super().__init__(dataio_type, public_type, size)
1150
1151 def get_code_fill(self, location: Location) -> str:
1152 raise NotImplementedError("fill not supported for memory-type fields")
1153
1154 def get_code_cmp(self, location: Location) -> str:
1155 if self.size.constant:
1156 return """\
1157differ = (memcmp(old->{location}, real_packet->{location}, {self.size.real}) != 0);
1158""".format(self = self, location = location)
1159 return """\
1160differ = (({self.size.old} != {self.size.real})
1161 || (memcmp(old->{location}, real_packet->{location}, {self.size.real}) != 0));
1162""".format(self = self, location = location)
1163
1164 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
1165 return """\
1166e |= DIO_PUT({self.dataio_type}, &dout, &field_addr, &real_packet->{location}, {self.size.real});
1167""".format(self = self, location = location)
1168
1169 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
1170 size_check = self.size.size_check_get(location.name)
1171 return """\
1172{size_check}\
1173if (!DIO_GET({self.dataio_type}, &din, &field_addr, real_packet->{location}, {self.size.real})) {{
1174 RECEIVE_PACKET_FIELD_ERROR({location.name});
1175}}
1176""".format(self = self, location = location, size_check = size_check)
1177
1178DEFAULT_REGISTRY.dataio_types["memory"] = partial(NeedSizeType, cls = MemoryType)
1179
1180
1181class ArrayType(FieldType):
1182 """Type information for an array field. Consists of size information and
1183 another FieldType for the array's elements, which may also be an
1184 ArrayType (for multi-dimensionaly arrays)."""
1185
1186 def __init__(self, elem: FieldType, size: SizeInfo):
1187 self.elem = elem
1188 """The type of the array elements"""
1189 self.size = size
1190 """The length (maximum and actual) of the array"""
1191
1192 def get_code_declaration(self, location: Location) -> str:
1193 return self.elem.get_code_declaration(
1194 location.deeper("%s[%s]" % (location, self.size.declared))
1195 )
1196
1197 def get_code_handle_param(self, location: Location) -> str:
1198 # add "const" if top level
1199 pre = "" if location.depth else "const "
1200 return pre + self.elem.get_code_handle_param(location.deeper("*%s" % location))
1201
1202 def get_code_fill(self, location: Location) -> str:
1203 inner_fill = prefix(" ", self.elem.get_code_fill(location.sub))
1204 return """\
1205{{
1206 int {location.index};
1207
1208 for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
1209{inner_fill}\
1210 }}
1211}}
1212""".format(self = self, location = location, inner_fill = inner_fill)
1213
1214 def get_code_hash(self, location: Location) -> str:
1215 raise ValueError("hash not supported for array type %s in field %s" % (self, location.name))
1216
1217 def get_code_cmp(self, location: Location) -> str:
1218 if not self.size.constant:
1219 head = """\
1220differ = ({self.size.old} != {self.size.real});
1221if (!differ) {{
1222""".format(self = self)
1223 else:
1224 head = """\
1225differ = FALSE;
1226{
1227"""
1228 inner_cmp = prefix(" ", self.elem.get_code_cmp(location.sub))
1229 return head + """\
1230 int {location.index};
1231
1232 for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
1233{inner_cmp}\
1234 if (differ) {{
1235 break;
1236 }}
1237 }}
1238}}
1239""".format(self = self, location = location, inner_cmp = inner_cmp)
1240
1241 def _get_code_put_full(self, location: Location, inner_put: str) -> str:
1242 """Helper method. Generate put code without array-diff."""
1243 inner_put = prefix(" ", inner_put)
1244 return """\
1245{{
1246 int {location.index};
1247
1248#ifdef FREECIV_JSON_CONNECTION
1249 /* Create the array. */
1250 e |= DIO_PUT(farray, &dout, &field_addr, {self.size.real});
1251
1252 /* Enter the array. */
1253 field_addr.sub_location = plocation_elem_new(0);
1254#endif /* FREECIV_JSON_CONNECTION */
1255
1256 for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
1257#ifdef FREECIV_JSON_CONNECTION
1258 /* Next array element. */
1259 field_addr.sub_location->number = {location.index};
1260#endif /* FREECIV_JSON_CONNECTION */
1261
1262{inner_put}\
1263 }}
1264
1265#ifdef FREECIV_JSON_CONNECTION
1266 /* Exit array. */
1267 FC_FREE(field_addr.sub_location);
1268#endif /* FREECIV_JSON_CONNECTION */
1269}}
1270""".format(self = self, location = location, inner_put = inner_put)
1271
1272 def _get_code_put_diff(self, location: Location, inner_put: str) -> str:
1273 """Helper method. Generate array-diff put code."""
1274 size_check = self.size.size_check_index(location.name)
1275 inner_put = prefix(" ", inner_put)
1276 inner_cmp = prefix(" ", self.elem.get_code_cmp(location.sub))
1277 index_put = prefix(" ", self.size.index_put(location.index))
1278 index_put_sentinel = prefix(" ", self.size.index_put(self.size.real))
1279 return """\
1280{size_check}\
1281{{
1282 int {location.index};
1283
1284#ifdef FREECIV_JSON_CONNECTION
1285 size_t count_{location.index} = 0;
1286
1287 /* Create the array. */
1288 e |= DIO_PUT(farray, &dout, &field_addr, 0);
1289
1290 /* Enter array. */
1291 field_addr.sub_location = plocation_elem_new(0);
1292#endif /* FREECIV_JSON_CONNECTION */
1293
1294 for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
1295{inner_cmp}\
1296
1297 if (differ) {{
1298#ifdef FREECIV_JSON_CONNECTION
1299 /* Append next diff array element. */
1300 field_addr.sub_location->number = -1;
1301
1302 /* Create the diff array element. */
1303 e |= DIO_PUT(farray, &dout, &field_addr, 2);
1304
1305 /* Enter diff array element (start at the index address). */
1306 field_addr.sub_location->number = count_{location.index}++;
1307 field_addr.sub_location->sub_location = plocation_elem_new(0);
1308#endif /* FREECIV_JSON_CONNECTION */
1309
1310 /* Write the index */
1311{index_put}\
1312
1313#ifdef FREECIV_JSON_CONNECTION
1314 /* Content address. */
1315 field_addr.sub_location->sub_location->number = 1;
1316#endif /* FREECIV_JSON_CONNECTION */
1317
1318{inner_put}\
1319
1320#ifdef FREECIV_JSON_CONNECTION
1321 /* Exit diff array element. */
1322 FC_FREE(field_addr.sub_location->sub_location);
1323#endif /* FREECIV_JSON_CONNECTION */
1324 }}
1325 }}
1326
1327#ifdef FREECIV_JSON_CONNECTION
1328 /* Append diff array element. */
1329 field_addr.sub_location->number = -1;
1330
1331 /* Create the terminating diff array element. */
1332 e |= DIO_PUT(farray, &dout, &field_addr, 1);
1333
1334 /* Enter diff array element (start at the index address). */
1335 field_addr.sub_location->number = count_{location.index};
1336 field_addr.sub_location->sub_location = plocation_elem_new(0);
1337#endif /* FREECIV_JSON_CONNECTION */
1338
1339 /* Write the sentinel value */
1340{index_put_sentinel}\
1341
1342#ifdef FREECIV_JSON_CONNECTION
1343 /* Exit diff array element. */
1344 FC_FREE(field_addr.sub_location->sub_location);
1345
1346 /* Exit array. */
1347 FC_FREE(field_addr.sub_location);
1348#endif /* FREECIV_JSON_CONNECTION */
1349}}
1350""".format(self = self, location = location, size_check = size_check, inner_cmp = inner_cmp,
1351 inner_put = inner_put, index_put = index_put, index_put_sentinel = index_put_sentinel)
1352
1353 def get_code_put(self, location: Location, deep_diff: bool = False) -> str:
1354 inner_put = self.elem.get_code_put(location.sub, deep_diff)
1355 if deep_diff:
1356 return self._get_code_put_diff(location, inner_put)
1357 else:
1358 return self._get_code_put_full(location, inner_put)
1359
1360 def _get_code_get_full(self, location: Location, inner_get: str) -> str:
1361 """Helper method. Generate get code without array-diff."""
1362 size_check = self.size.size_check_get(location.name)
1363 inner_get = prefix(" ", inner_get)
1364 return """\
1365{size_check}\
1366{{
1367 int {location.index};
1368
1369#ifdef FREECIV_JSON_CONNECTION
1370 /* Enter array. */
1371 field_addr.sub_location = plocation_elem_new(0);
1372#endif /* FREECIV_JSON_CONNECTION */
1373
1374 for ({location.index} = 0; {location.index} < {self.size.real}; {location.index}++) {{
1375#ifdef FREECIV_JSON_CONNECTION
1376 field_addr.sub_location->number = {location.index};
1377#endif /* FREECIV_JSON_CONNECTION */
1378
1379{inner_get}\
1380 }}
1381
1382#ifdef FREECIV_JSON_CONNECTION
1383 /* Exit array. */
1384 FC_FREE(field_addr.sub_location);
1385#endif /* FREECIV_JSON_CONNECTION */
1386}}
1387""".format(self = self, location = location, inner_get = inner_get, size_check = size_check)
1388
1389 def _get_code_get_diff(self, location: Location, inner_get: str) -> str:
1390 """Helper method. Generate array-diff get code."""
1391 size_check = self.size.size_check_get(location.name) + self.size.size_check_index(location.name)
1392 inner_get = prefix(" ", inner_get)
1393 index_get = prefix(" ", self.size.index_get(location))
1394 return """\
1395{size_check}\
1396#ifdef FREECIV_JSON_CONNECTION
1397/* Enter array (start at initial element). */
1398field_addr.sub_location = plocation_elem_new(0);
1399/* Enter diff array element (start at the index address). */
1400field_addr.sub_location->sub_location = plocation_elem_new(0);
1401#endif /* FREECIV_JSON_CONNECTION */
1402
1403while (TRUE) {{
1404 int {location.index};
1405
1406 /* Read next index */
1407{index_get}\
1408
1409 if ({location.index} == {self.size.real}) {{
1410 break;
1411 }}
1412 if ({location.index} > {self.size.real}) {{
1413 RECEIVE_PACKET_FIELD_ERROR({location.name},
1414 ": unexpected value %d "
1415 "(> {self.size.real}) in array diff",
1416 {location.index});
1417 }}
1418
1419#ifdef FREECIV_JSON_CONNECTION
1420 /* Content address. */
1421 field_addr.sub_location->sub_location->number = 1;
1422#endif /* FREECIV_JSON_CONNECTION */
1423
1424{inner_get}\
1425
1426#ifdef FREECIV_JSON_CONNECTION
1427 /* Move to the next diff array element. */
1428 field_addr.sub_location->number++;
1429 /* Back to the index address. */
1430 field_addr.sub_location->sub_location->number = 0;
1431#endif /* FREECIV_JSON_CONNECTION */
1432}}
1433
1434#ifdef FREECIV_JSON_CONNECTION
1435/* Exit diff array element. */
1436FC_FREE(field_addr.sub_location->sub_location);
1437/* Exit array. */
1438FC_FREE(field_addr.sub_location);
1439#endif /* FREECIV_JSON_CONNECTION */
1440""".format(self = self, location = location, size_check = size_check,
1441 inner_get = inner_get, index_get = index_get)
1442
1443 def get_code_get(self, location: Location, deep_diff: bool = False) -> str:
1444 inner_get = self.elem.get_code_get(location.sub, deep_diff)
1445 if deep_diff:
1446 return self._get_code_get_diff(location, inner_get)
1447 else:
1448 return self._get_code_get_full(location, inner_get)
1449
1450 def __str__(self) -> str:
1451 return "{self.elem}[{self.size}]".format(self = self)
1452
1453
1454class Field:
1455 """A single field of a packet. Consists of a name, type information
1456 (including array sizes) and flags."""
1457
1458 FIELDS_LINE_PATTERN = re.compile(r"^\s*(\w+(?:\‍([^()]*\‍))?)\s+([^;()]*?)\s*;\s*(.*?)\s*$")
1459 """Matches an entire field definition line.
1460
1461 Groups:
1462 - type
1463 - field names and array sizes
1464 - flags"""
1465
1466 FIELD_ARRAY_PATTERN = re.compile(r"^(.+)\[([^][]+)\]$")
1467 """Matches a field definition with one or more array sizes
1468
1469 Groups:
1470 - everything except the final array size
1471 - the final array size"""
1472
1473 @classmethod
1474 def parse(cls, cfg: ScriptConfig, line: str, resolve_type: typing.Callable[[str], RawFieldType]) -> "typing.Iterable[Field]":
1475 """Parse a single line defining one or more fields"""
1476 mo = cls.FIELDS_LINE_PATTERN.fullmatch(line)
1477 if mo is None:
1478 raise ValueError("invalid field definition: %r" % line)
1479 type_text, fields, flags = (i.strip() for i in mo.groups(""))
1480
1481 type_info = resolve_type(type_text)
1482 flag_info = FieldFlags.parse(flags)
1483
1484 # analyze fields
1485 for field_text in fields.split(","):
1486 field_text = field_text.strip()
1487 field_type = type_info
1488
1489 mo = cls.FIELD_ARRAY_PATTERN.fullmatch(field_text)
1490 while mo is not None:
1491 field_text = mo.group(1)
1492 field_type = field_type.array(SizeInfo.parse(mo.group(2)))
1493 mo = cls.FIELD_ARRAY_PATTERN.fullmatch(field_text)
1494
1495 if not isinstance(field_type, FieldType):
1496 raise ValueError("need an array size to use type %s" % field_type)
1497
1498 yield Field(cfg, field_text, field_type, flag_info)
1499
1500 def __init__(self, cfg: ScriptConfig, name: str, type_info: FieldType, flags: FieldFlags):
1501 self.cfg = cfg
1502 """Configuration used when generating code for this field"""
1503 self.name = name
1504 """This field's name (identifier)"""
1505
1506 self.type_info = type_info
1507 """This field's type information; see FieldType"""
1508 self.flags = flags
1509 """This field's flags; see FieldFlags"""
1510
1511 @property
1512 def is_key(self) -> bool:
1513 """Whether this is a key field"""
1514 return self.flags.is_key
1515
1516 @property
1517 def diff(self) -> bool:
1518 """Whether this field uses deep diff / array-diff when transmitted
1519 as part of a delta packet"""
1520 return self.flags.diff
1521
1522 @property
1523 def all_caps(self) -> "typing.AbstractSet[str]":
1524 """Set of all capabilities affecting this field"""
1525 return self.flags.add_caps | self.flags.remove_caps
1526
1527 def is_compatible(self, other: "Field") -> bool:
1528 """Whether two field objects are variants of the same field, i.e.
1529 type-compatible in the packet struct and mutually exclusive based
1530 on their required capabilities.
1531
1532 Note that this function does not test field name."""
1533 return bool(
1534 (
1535 (self.flags.add_caps & other.flags.remove_caps)
1536 or
1537 (self.flags.remove_caps & other.flags.add_caps)
1538 )
1539 and
1540 self.type_info.is_type_compatible(other.type_info)
1541 )
1542
1543 def present_with_caps(self, caps: typing.Container[str]) -> bool:
1544 """Determine whether this field should be part of a variant with the
1545 given capabilities"""
1546 return (
1547 all(cap in caps for cap in self.flags.add_caps)
1548 ) and (
1549 all(cap not in caps for cap in self.flags.remove_caps)
1550 )
1551
1552 def get_declar(self) -> str:
1553 """Generate the way this field is declared in the packet struct"""
1554 return self.type_info.get_code_declaration(Location(self.name))
1555
1556 def get_handle_param(self) -> str:
1557 """Generate the way this field is declared as a parameter of a
1558 handle function.
1559
1560 See also self.get_handle_arg()"""
1561 return self.type_info.get_code_handle_param(Location(self.name))
1562
1563 def get_handle_arg(self, packet_arrow: str) -> str:
1564 """Generate the way this field is passed as an argument to a handle
1565 function.
1566
1567 See also self.get_handle_param()"""
1568 return self.type_info.get_code_handle_arg(Location(
1569 self.name,
1570 packet_arrow + self.name,
1571 ))
1572
1573 def get_fill(self) -> str:
1574 """Generate code moving this field from the dsend arguments into
1575 the packet struct."""
1576 return self.type_info.get_code_fill(Location(self.name))
1577
1578 def get_hash(self) -> str:
1579 """Generate code factoring this field into a hash computation."""
1580 assert self.is_key
1581 return self.type_info.get_code_hash(Location(self.name))
1582
1583 def get_cmp(self) -> str:
1584 """Generate code checking whether this field changed.
1585
1586 This code is primarily used by self.get_cmp_wrapper()"""
1587 return self.type_info.get_code_cmp(Location(self.name))
1588
1589 @property
1590 def folded_into_head(self) -> bool:
1591 """Whether this field is folded into the packet header.
1592
1593 If enabled, lone booleans (which only carry one bit of information)
1594 get directly written into the `fields` bitvector, since they don't
1595 take any more space than the usual "content-differs" bit would.
1596
1597 See also get_cmp_wrapper()"""
1598 return (
1599 self.cfg.fold_bool
1600 and self.type_info.foldable
1601 )
1602
1603 def get_cmp_wrapper(self, i: int, pack: "Variant") -> str:
1604 """Generate code setting this field's bit in the `fields` bitvector.
1605
1606 This bit marks whether the field changed and is being transmitted,
1607 except for (non-array) boolean fields folded into the header;
1608 see self.folded_into_head for more details.
1609
1610 See also self.get_cmp()"""
1611 if self.folded_into_head:
1612 if pack.is_info != "no":
1613 cmp = self.get_cmp()
1614 differ_part = """\
1615if (differ) {
1616 different++;
1617}
1618"""
1619 else:
1620 cmp = ""
1621 differ_part = ""
1622 b = "packet->{self.name}".format(self = self)
1623 return cmp + differ_part + """\
1624if (%s) {
1625 BV_SET(fields, %d);
1626}
1627
1628""" % (b, i)
1629 else:
1630 cmp = self.get_cmp()
1631 if pack.is_info != "no":
1632 return """\
1633%s\
1634if (differ) {
1635 different++;
1636 BV_SET(fields, %d);
1637}
1638
1639""" % (cmp, i)
1640 else:
1641 return """\
1642%s\
1643if (differ) {
1644 BV_SET(fields, %d);
1645}
1646
1647""" % (cmp, i)
1648
1649 def get_put_wrapper(self, packet: "Variant", i: int, deltafragment: bool) -> str:
1650 """Generate code conditionally putting this field iff its bit in the
1651 `fields` bitvector is set.
1652
1653 Does nothing for boolean fields folded into the packet header.
1654
1655 See also self.get_put()"""
1656 if self.folded_into_head:
1657 return """\
1658/* field {i:d} is folded into the header */
1659""".format(i = i)
1660 put = prefix(" ", self.get_put(deltafragment))
1661 if packet.gen_log:
1662 f = """\
1663 {packet.log_macro}(" field \'{self.name}\' has changed");
1664""".format(packet = packet, self = self)
1665 else:
1666 f=""
1667 if packet.gen_stats:
1668 s = """\
1669 stats_{packet.name}_counters[{i:d}]++;
1670""".format(packet = packet, i = i)
1671 else:
1672 s=""
1673 return """\
1674if (BV_ISSET(fields, {i:d})) {{
1675{f}\
1676{s}\
1677{put}\
1678}}
1679""".format(i = i, f = f, s = s, put = put)
1680
1681 def get_put(self, deltafragment: bool) -> str:
1682 """Generate the code putting this field, i.e. writing it to the
1683 dataio stream.
1684
1685 This does not include delta-related code checking whether to
1686 transmit the field in the first place; see self.get_put_wrapper()"""
1687 real = self.get_put_real(deltafragment)
1688 return """\
1689#ifdef FREECIV_JSON_CONNECTION
1690field_addr.name = "{self.name}";
1691#endif /* FREECIV_JSON_CONNECTION */
1692e = 0;
1693{real}\
1694if (e) {{
1695 log_packet_detailed("'{self.name}' field error detected");
1696}}
1697""".format(self = self, real = real)
1698
1699 def get_put_real(self, deltafragment: bool) -> str:
1700 """Generate the bare core of this field's put code. This code is not
1701 yet wrapped for full delta and JSON protocol support.
1702
1703 See self.get_put() for more info"""
1704 return self.type_info.get_code_put(Location(self.name), deltafragment and self.diff)
1705
1706 def get_get_wrapper(self, packet: "Variant", i: int, deltafragment: bool) -> str:
1707 """Generate code conditionally getting this field iff its bit in the
1708 `fields` bitvector is set.
1709
1710 For boolean fields folded into the packet header, instead reads the
1711 field from the bitvector.
1712
1713 See also self.get_get()"""
1714 if self.folded_into_head:
1715 return """\
1716real_packet->{self.name} = BV_ISSET(fields, {i:d});
1717""".format(self = self, i = i)
1718 get = prefix(" ", self.get_get(deltafragment))
1719 if packet.gen_log:
1720 f = """\
1721 {packet.log_macro}(" got field '{self.name}'");
1722""".format(self = self, packet = packet)
1723 else:
1724 f=""
1725 return """\
1726if (BV_ISSET(fields, {i:d})) {{
1727{f}\
1728{get}\
1729}}
1730""".format(i = i, f = f, get = get)
1731
1732 def get_get(self, deltafragment: bool) -> str:
1733 """Generate the code getting this field, i.e. reading it from the
1734 dataio stream.
1735
1736 This does not include delta-related code checking if the field
1737 was transmitted in the first place; see self.get_get_wrapper()"""
1738 return """\
1739#ifdef FREECIV_JSON_CONNECTION
1740field_addr.name = \"{self.name}\";
1741#endif /* FREECIV_JSON_CONNECTION */
1742""".format(self = self) + self.get_get_real(deltafragment)
1743
1744 def get_get_real(self, deltafragment: bool) -> str:
1745 """Generate the bare core of this field's get code. This code is not
1746 yet wrapped for full delta and JSON protocol support.
1747
1748 See self.get_get() for more info"""
1749 return self.type_info.get_code_get(Location(self.name), deltafragment and self.diff)
1750
1751
1752class Variant:
1753 """Represents one variant of a packet. Packets with add-cap or
1754 remove-cap fields have different variants for different combinations of
1755 the relevant optional capabilities."""
1756
1757 def __init__(self, poscaps: typing.Iterable[str], negcaps: typing.Iterable[str],
1758 packet: "Packet", no: int):
1759 self.packet = packet
1760 """The packet this is a variant of"""
1761 self.no=no
1762 """The numeric variant ID (not packet ID) of this variant"""
1763 self.name = "%s_%d" % (packet.name, no)
1764 """The full name of this variant"""
1765
1766 self.poscaps = set(poscaps)
1767 """The set of optional capabilities that must be present to use this
1768 variant"""
1769 self.negcaps = set(negcaps)
1770 """The set of optional capabilities that must *not* be present to
1771 use this variant"""
1772 self.fields = [
1773 field
1774 for field in packet.all_fields
1775 if field.present_with_caps(self.poscaps)
1776 ]
1777 """All fields that are transmitted when using this variant"""
1778 self.key_fields = [field for field in self.fields if field.is_key]
1779 """The key fields that are used for this variant"""
1780 self.other_fields = [field for field in self.fields if not field.is_key]
1781 """The non-key fields that are transmitted when using this variant"""
1782 # FIXME: Doesn't work with non-int key fields
1783 self.keys_format=", ".join(["%d"]*len(self.key_fields))
1784 """The printf format string for this variant's key fields in
1785 generated log calls
1786
1787 See also self.keys_arg"""
1788 self.keys_arg = ", ".join("real_packet->" + field.name for field in self.key_fields)
1789 """The arguments passed when formatting this variant's key fields in
1790 generated log calls
1791
1792 See also self.keys_format"""
1793 if self.keys_arg:
1794 self.keys_arg=",\n "+self.keys_arg
1795
1796 if not self.fields and packet.fields:
1797 raise ValueError("empty variant for nonempty {self.packet_name} with capabilities {self.poscaps}".format(self = self))
1798
1799 @property
1800 def cfg(self) -> ScriptConfig:
1801 """Configuration used when generating code for this packet
1802 variant
1803
1804 See self.packet and Packet.cfg"""
1805 return self.packet.cfg
1806
1807 @property
1808 def gen_stats(self) -> bool:
1809 """Whether to generate delta stats code for this packet variant
1810
1811 See self.cfg and ScriptConfig.gen_stats"""
1812 return self.cfg.gen_stats
1813
1814 @property
1815 def log_macro(self) -> "str | None":
1816 """The log macro used to generate log calls for this packet variant,
1817 or None if no log calls should be generated
1818
1819 See self.cfg and ScriptConfig.log_macro"""
1820 return self.cfg.log_macro
1821
1822 @property
1823 def gen_log(self) -> bool:
1824 """Whether to generate log calls for this packet variant
1825
1826 See self.log_macro"""
1827 return self.log_macro is not None
1828
1829 @property
1830 def packet_name(self) -> str:
1831 """Name of the packet this is a variant of
1832
1833 See Packet.name"""
1834 return self.packet.name
1835
1836 @property
1837 def type(self) -> str:
1838 """Type (enum constant) of the packet this is a variant of
1839
1840 See Packet.type"""
1841 return self.packet.type
1842
1843 @property
1844 def no_packet(self) -> bool:
1845 """Whether the send function should not take/need a packet struct
1846
1847 See Packet.no_packet"""
1848 return self.packet.no_packet
1849
1850 @property
1851 def delta(self) -> bool:
1852 """Whether this packet can use delta optimization
1853
1854 See Packet.delta"""
1855 return self.packet.delta
1856
1857 @property
1858 def want_force(self):
1859 """Whether send function takes a force_to_send boolean
1860
1861 See Packet.want_force"""
1862 return self.packet.want_force
1863
1864 @property
1865 def is_info(self) -> str:
1866 """Whether this is an info or game-info packet"""
1867 return self.packet.is_info
1868
1869 @property
1870 def cancel(self) -> "list[str]":
1871 """List of packets to cancel when sending or receiving this packet
1872
1873 See Packet.cancel"""
1874 return self.packet.cancel
1875
1876 @property
1877 def differ_used(self) -> bool:
1878 """Whether the send function needs a `differ` boolean.
1879
1880 See get_send()"""
1881 return (
1882 (not self.no_packet)
1883 and self.delta
1884 and (
1885 self.is_info != "no"
1886 or any(
1887 not field.folded_into_head
1888 for field in self.other_fields
1889 )
1890 )
1891 )
1892
1893 @property
1894 def condition(self) -> str:
1895 """The condition determining whether this variant should be used,
1896 based on capabilities.
1897
1898 See get_packet_handlers_fill_capability()"""
1899 if self.poscaps or self.negcaps:
1900 cap_fmt = "has_capability(\"%s\", capability)"
1901 return " && ".join(chain(
1902 (cap_fmt % cap for cap in sorted(self.poscaps)),
1903 ("!" + cap_fmt % cap for cap in sorted(self.negcaps)),
1904 ))
1905 else:
1906 return "TRUE"
1907
1908 @property
1909 def bits(self) -> int:
1910 """The length of the bitvector for this variant."""
1911 return len(self.other_fields)
1912
1913 @property
1914 def receive_prototype(self) -> str:
1915 """The prototype of this variant's receive function"""
1916 return "static struct {self.packet_name} *receive_{self.name}(struct connection *pc)".format(self = self)
1917
1918 @property
1919 def send_prototype(self) -> str:
1920 """The prototype of this variant's send function"""
1921 return "static int send_{self.name}(struct connection *pc{self.packet.extra_send_args})".format(self = self)
1922
1923 @property
1924 def send_handler(self) -> str:
1925 """Code to set the send handler for this variant
1926
1927 See get_packet_handlers_fill_initial and
1928 get_packet_handlers_fill_capability"""
1929 if self.no_packet:
1930 return """\
1931phandlers->send[{self.type}].no_packet = (int(*)(struct connection *)) send_{self.name};
1932""".format(self = self)
1933 elif self.want_force:
1934 return """\
1935phandlers->send[{self.type}].force_to_send = (int(*)(struct connection *, const void *, bool)) send_{self.name};
1936""".format(self = self)
1937 else:
1938 return """\
1939phandlers->send[{self.type}].packet = (int(*)(struct connection *, const void *)) send_{self.name};
1940""".format(self = self)
1941
1942 @property
1943 def receive_handler(self) -> str:
1944 """Code to set the receive handler for this variant
1945
1946 See get_packet_handlers_fill_initial and
1947 get_packet_handlers_fill_capability"""
1948 return """\
1949phandlers->receive[{self.type}] = (void *(*)(struct connection *)) receive_{self.name};
1950""".format(self = self)
1951
1952 def get_stats(self) -> str:
1953 """Generate the declaration of the delta stats counters associated
1954 with this packet variant"""
1955 names = ", ".join(
1956 "\"%s\"" % field.name
1957 for field in self.other_fields
1958 )
1959
1960 return """\
1961static int stats_{self.name}_sent;
1962static int stats_{self.name}_discarded;
1963static int stats_{self.name}_counters[{self.bits:d}];
1964static char *stats_{self.name}_names[] = {{{names}}};
1965
1966""".format(self = self, names = names)
1967
1968 def get_bitvector(self) -> str:
1969 """Generate the declaration of the fields bitvector type for this
1970 packet variant"""
1971 return """\
1972BV_DEFINE({self.name}_fields, {self.bits});
1973""".format(self = self)
1974
1975 def get_report_part(self) -> str:
1976 """Generate the part of the delta_stats_report8) function specific
1977 to this packet variant"""
1978 return """\
1979
1980if (stats_{self.name}_sent > 0
1981 && stats_{self.name}_discarded != stats_{self.name}_sent) {{
1982 log_test(\"{self.name} %d out of %d got discarded\",
1983 stats_{self.name}_discarded, stats_{self.name}_sent);
1984 for (i = 0; i < {self.bits}; i++) {{
1985 if (stats_{self.name}_counters[i] > 0) {{
1986 log_test(\" %4d / %4d: %2d = %s\",
1987 stats_{self.name}_counters[i],
1988 (stats_{self.name}_sent - stats_{self.name}_discarded),
1989 i, stats_{self.name}_names[i]);
1990 }}
1991 }}
1992}}
1993""".format(self = self)
1994
1995 def get_reset_part(self) -> str:
1996 """Generate the part of the delta_stats_reset() function specific
1997 to this packet variant"""
1998 return """\
1999stats_{self.name}_sent = 0;
2000stats_{self.name}_discarded = 0;
2001memset(stats_{self.name}_counters, 0,
2002 sizeof(stats_{self.name}_counters));
2003""".format(self = self)
2004
2005 def get_hash(self) -> str:
2006 """Generate the key hash function for this variant"""
2007 if not self.key_fields:
2008 return """\
2009#define hash_{self.name} hash_const
2010
2011""".format(self = self)
2012
2013 intro = """\
2014static genhash_val_t hash_{self.name}(const void *vkey)
2015{{
2016 const struct {self.packet_name} *key = (const struct {self.packet_name} *) vkey;
2017 genhash_val_t result = 0;
2018
2019""".format(self = self)
2020
2021 body = """\
2022
2023 result *= 5;
2024
2025""".join(prefix(" ", field.get_hash()) for field in self.key_fields)
2026
2027 extro = """\
2028
2029 result &= 0xFFFFFFFF;
2030 return result;
2031}
2032
2033"""
2034
2035 return intro + body + extro
2036
2037 def get_cmp(self) -> str:
2038 """Generate the key comparison function for this variant"""
2039 if not self.key_fields:
2040 return """\
2041#define cmp_{self.name} cmp_const
2042
2043""".format(self = self)
2044
2045 # note: the names `old` and `real_packet` allow reusing
2046 # field-specific cmp code
2047 intro = """\
2048static bool cmp_{self.name}(const void *vkey1, const void *vkey2)
2049{{
2050 const struct {self.packet_name} *old = (const struct {self.packet_name} *) vkey1;
2051 const struct {self.packet_name} *real_packet = (const struct {self.packet_name} *) vkey2;
2052 bool differ;
2053
2054""".format(self = self)
2055
2056 body = """\
2057
2058 if (differ) {
2059 return !differ;
2060 }
2061
2062""".join(prefix(" ", field.get_cmp()) for field in self.key_fields)
2063
2064 extro = """\
2065
2066 return !differ;
2067}
2068"""
2069
2070 return intro + body + extro
2071
2072 def get_send(self) -> str:
2073 """Generate the send function for this packet variant"""
2074 if self.gen_stats:
2075 report = """\
2076
2077 stats_total_sent++;
2078 stats_{self.name}_sent++;
2079""".format(self = self)
2080 else:
2081 report=""
2082 if self.gen_log:
2083 log = """\
2084
2085 {self.log_macro}("{self.name}: sending info about ({self.keys_format})"{self.keys_arg});
2086""".format(self = self)
2087 else:
2088 log=""
2089
2090 if self.no_packet:
2091 main_header = ""
2092 else:
2093 if self.packet.want_pre_send:
2094 main_header = """\
2095 /* copy packet for pre-send */
2096 struct {self.packet_name} packet_buf = *packet;
2097 const struct {self.packet_name} *real_packet = &packet_buf;
2098""".format(self = self)
2099 else:
2100 main_header = """\
2101 const struct {self.packet_name} *real_packet = packet;
2102""".format(self = self)
2103 main_header += """\
2104 int e;
2105"""
2106
2107 if not self.packet.want_pre_send:
2108 pre = ""
2109 elif self.no_packet:
2110 pre = """\
2111
2112 pre_send_{self.packet_name}(pc, NULL);
2113""".format(self = self)
2114 else:
2115 pre = """\
2116
2117 pre_send_{self.packet_name}(pc, &packet_buf);
2118""".format(self = self)
2119
2120 if not self.no_packet:
2121 if self.delta:
2122 if self.want_force:
2123 diff = "force_to_send"
2124 else:
2125 diff = "0"
2126 delta_header = """\
2127#ifdef FREECIV_DELTA_PROTOCOL
2128 {self.name}_fields fields;
2129 struct {self.packet_name} *old;
2130""".format(self = self)
2131 if self.differ_used:
2132 delta_header += """\
2133 bool differ;
2134"""
2135 delta_header += """\
2136 struct genhash **hash = pc->phs.sent + {self.type};
2137""".format(self = self)
2138 if self.is_info != "no":
2139 delta_header += """\
2140 int different = {diff};
2141""".format(diff = diff)
2142 delta_header += """\
2143#endif /* FREECIV_DELTA_PROTOCOL */
2144"""
2145 body = prefix(" ", self.get_delta_send_body()) + """\
2146#ifndef FREECIV_DELTA_PROTOCOL
2147"""
2148 else:
2149 delta_header=""
2150 body = """\
2151#if 1 /* To match endif */
2152"""
2153 body += "".join(
2154 prefix(" ", field.get_put(False))
2155 for field in self.fields
2156 )
2157 body += """\
2158
2159#endif
2160"""
2161 else:
2162 body=""
2163 delta_header=""
2164
2165 if self.packet.want_post_send:
2166 if self.no_packet:
2167 post = """\
2168 post_send_{self.packet_name}(pc, NULL);
2169""".format(self = self)
2170 else:
2171 post = """\
2172 post_send_{self.packet_name}(pc, real_packet);
2173""".format(self = self)
2174 else:
2175 post=""
2176
2177 if self.fields:
2178 faddr = """\
2179#ifdef FREECIV_JSON_CONNECTION
2180 struct plocation field_addr;
2181 {
2182 struct plocation *field_addr_tmp = plocation_field_new(NULL);
2183 field_addr = *field_addr_tmp;
2184 FC_FREE(field_addr_tmp);
2185 }
2186#endif /* FREECIV_JSON_CONNECTION */
2187"""
2188 else:
2189 faddr = ""
2190
2191 return "".join((
2192 """\
2193{self.send_prototype}
2194{{
2195""".format(self = self),
2196 main_header,
2197 delta_header,
2198 """\
2199 SEND_PACKET_START({self.type});
2200""".format(self = self),
2201 faddr,
2202 log,
2203 report,
2204 pre,
2205 body,
2206 post,
2207 """\
2208 SEND_PACKET_END({self.type});
2209}}
2210
2211""".format(self = self),
2212 ))
2213
2214 def get_delta_send_body(self, before_return: str = "") -> str:
2215 """Helper for get_send(). Generate the part of the send function
2216 that computes and transmits the delta between the real packet and
2217 the last cached packet."""
2218 intro = """\
2219
2220#ifdef FREECIV_DELTA_PROTOCOL
2221if (NULL == *hash) {{
2222 *hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
2223 NULL, NULL, NULL, free);
2224}}
2225BV_CLR_ALL(fields);
2226
2227if (!genhash_lookup(*hash, real_packet, (void **) &old)) {{
2228 old = fc_malloc(sizeof(*old));
2229 *old = *real_packet;
2230 genhash_insert(*hash, old, old);
2231 memset(old, 0, sizeof(*old));
2232""".format(self = self)
2233 if self.is_info != "no":
2234 intro += """\
2235 different = 1; /* Force to send. */
2236"""
2237 intro += """\
2238}
2239"""
2240 body = "".join(
2241 field.get_cmp_wrapper(i, self)
2242 for i, field in enumerate(self.other_fields)
2243 )
2244 if self.gen_log:
2245 fl = """\
2246 {self.log_macro}(" no change -> discard");
2247""".format(self = self)
2248 else:
2249 fl=""
2250 if self.gen_stats:
2251 s = """\
2252 stats_{self.name}_discarded++;
2253""".format(self = self)
2254 else:
2255 s=""
2256
2257 if self.is_info != "no":
2258 body += """\
2259if (different == 0) {{
2260{fl}\
2261{s}\
2262{before_return}\
2263 SEND_PACKET_DISCARD();
2264}}
2265""".format(fl = fl, s = s, before_return = before_return)
2266
2267 body += """\
2268
2269#ifdef FREECIV_JSON_CONNECTION
2270field_addr.name = "fields";
2271#endif /* FREECIV_JSON_CONNECTION */
2272e = 0;
2273e |= DIO_BV_PUT(&dout, &field_addr, fields);
2274if (e) {
2275 log_packet_detailed("fields bitvector error detected");
2276}
2277"""
2278
2279 body += "".join(
2280 field.get_put(True)
2281 for field in self.key_fields
2282 )
2283 body += "\n"
2284
2285 body += "".join(
2286 field.get_put_wrapper(self, i, True)
2287 for i, field in enumerate(self.other_fields)
2288 )
2289 body += """\
2290
2291*old = *real_packet;
2292"""
2293
2294 # Cancel some is-info packets.
2295 for i in self.cancel:
2296 body += """\
2297
2298hash = pc->phs.sent + %s;
2299if (NULL != *hash) {
2300 genhash_remove(*hash, real_packet);
2301}
2302""" % i
2303 body += """\
2304#endif /* FREECIV_DELTA_PROTOCOL */
2305"""
2306
2307 return intro+body
2308
2309 def get_receive(self) -> str:
2310 """Generate the receive function for this packet variant"""
2311 if self.delta:
2312 delta_header = """\
2313#ifdef FREECIV_DELTA_PROTOCOL
2314 {self.name}_fields fields;
2315 struct {self.packet_name} *old;
2316 struct genhash **hash = pc->phs.received + {self.type};
2317#endif /* FREECIV_DELTA_PROTOCOL */
2318""".format(self = self)
2319 delta_body1 = """\
2320
2321#ifdef FREECIV_DELTA_PROTOCOL
2322#ifdef FREECIV_JSON_CONNECTION
2323 field_addr.name = "fields";
2324#endif /* FREECIV_JSON_CONNECTION */
2325 DIO_BV_GET(&din, &field_addr, fields);
2326"""
2327 body1 = "".join(
2328 prefix(" ", field.get_get(True))
2329 for field in self.key_fields
2330 )
2331 body1 += """\
2332
2333#else /* FREECIV_DELTA_PROTOCOL */
2334"""
2335 body2 = prefix(" ", self.get_delta_receive_body())
2336 else:
2337 delta_header=""
2338 delta_body1=""
2339 body1 = """\
2340#if 1 /* To match endif */
2341"""
2342 body2=""
2343 nondelta = "".join(
2344 prefix(" ", field.get_get(False))
2345 for field in self.fields
2346 ) or """\
2347 real_packet->__dummy = 0xff;
2348"""
2349 body1 += nondelta + """\
2350#endif
2351"""
2352
2353 if self.gen_log:
2354 log = """\
2355 {self.log_macro}("{self.name}: got info about ({self.keys_format})"{self.keys_arg});
2356""".format(self = self)
2357 else:
2358 log=""
2359
2360 if self.packet.want_post_recv:
2361 post = """\
2362 post_receive_{self.packet_name}(pc, real_packet);
2363""".format(self = self)
2364 else:
2365 post=""
2366
2367 if self.fields:
2368 faddr = """\
2369#ifdef FREECIV_JSON_CONNECTION
2370 struct plocation field_addr;
2371 {
2372 struct plocation *field_addr_tmp = plocation_field_new(NULL);
2373 field_addr = *field_addr_tmp;
2374 FC_FREE(field_addr_tmp);
2375 }
2376#endif /* FREECIV_JSON_CONNECTION */
2377"""
2378 else:
2379 faddr = ""
2380
2381 return "".join((
2382 """\
2383{self.receive_prototype}
2384{{
2385""".format(self = self),
2386 delta_header,
2387 """\
2388 RECEIVE_PACKET_START({self.packet_name}, real_packet);
2389""".format(self = self),
2390 faddr,
2391 delta_body1,
2392 body1,
2393 log,
2394 body2,
2395 post,
2396 """\
2397 RECEIVE_PACKET_END(real_packet);
2398}
2399
2400""",
2401 ))
2402
2403 def get_delta_receive_body(self) -> str:
2404 """Helper for get_receive(). Generate the part of the receive
2405 function responsible for recreating the full packet from the
2406 received delta and the last cached packet."""
2407 if self.key_fields:
2408 # bit-copy the values, since we're moving (not cloning)
2409 # the key fields
2410 # FIXME: might not work for arrays
2411 backup_key = "".join(
2412 prefix(" ", field.get_declar())
2413 for field in self.key_fields
2414 ) + "\n"+ "".join(
2415 """\
2416 {field.name} = real_packet->{field.name};
2417""".format(field = field)
2418 for field in self.key_fields
2419 ) + "\n"
2420 restore_key = "\n" + "".join(
2421 """\
2422 real_packet->{field.name} = {field.name};
2423""".format(field = field)
2424 for field in self.key_fields
2425 )
2426 else:
2427 backup_key = restore_key = ""
2428 if self.gen_log:
2429 fl = """\
2430 {self.log_macro}(" no old info");
2431""".format(self = self)
2432 else:
2433 fl=""
2434 body = """\
2435
2436#ifdef FREECIV_DELTA_PROTOCOL
2437if (NULL == *hash) {{
2438 *hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
2439 NULL, NULL, NULL, free);
2440}}
2441
2442if (genhash_lookup(*hash, real_packet, (void **) &old)) {{
2443 *real_packet = *old;
2444}} else {{
2445{backup_key}\
2446{fl}\
2447 memset(real_packet, 0, sizeof(*real_packet));
2448{restore_key}\
2449}}
2450
2451""".format(self = self, backup_key = backup_key, restore_key = restore_key, fl = fl)
2452 body += "".join(
2453 field.get_get_wrapper(self, i, True)
2454 for i, field in enumerate(self.other_fields)
2455 )
2456
2457 extro = """\
2458
2459if (NULL == old) {
2460 old = fc_malloc(sizeof(*old));
2461 *old = *real_packet;
2462 genhash_insert(*hash, old, old);
2463} else {
2464 *old = *real_packet;
2465}
2466"""
2467
2468 # Cancel some is-info packets.
2469 extro += "".join(
2470 """\
2471
2472hash = pc->phs.received + %s;
2473if (NULL != *hash) {
2474 genhash_remove(*hash, real_packet);
2475}
2476""" % cancel_pack
2477 for cancel_pack in self.cancel
2478 )
2479
2480 return body + extro + """\
2481
2482#endif /* FREECIV_DELTA_PROTOCOL */
2483"""
2484
2485
2486class Directions(Enum):
2487 """Describes the possible combinations of directions for which a packet
2488 can be valid"""
2489
2490 # Note: "sc" and "cs" are used to match the packet flags
2491
2492 DOWN_ONLY = frozenset({"sc"})
2493 """Packet may only be sent from server to client"""
2494
2495 UP_ONLY = frozenset({"cs"})
2496 """Packet may only be sent from client to server"""
2497
2498 UNRESTRICTED = frozenset({"sc", "cs"})
2499 """Packet may be sent both ways"""
2500
2501 @property
2502 def down(self) -> bool:
2503 """Whether a packet may be sent from server to client"""
2504 return "sc" in self.value
2505
2506 @property
2507 def up(self) -> bool:
2508 """Whether a packet may be sent from client to server"""
2509 return "cs" in self.value
2510
2511
2512class Packet:
2513 """Represents a single packet type (possibly with multiple variants)"""
2514
2515 CANCEL_PATTERN = re.compile(r"^cancel\‍((.*)\‍)$")
2516 """Matches a cancel flag
2517
2518 Groups:
2519 - the packet type to cancel"""
2520
2521 is_info = "no"
2522 """Whether this is an is-info or is-game-info packet.
2523 "no" means normal, "yes" means is-info, "game" means is-game-info"""
2524
2525 want_dsend = False
2526 """Whether to generate a direct-send function taking field values
2527 instead of a packet struct"""
2528
2529 want_lsend = False
2530 """Whether to generate a list-send function sending a packet to
2531 multiple connections"""
2532
2533 want_force = False
2534 """Whether send functions should take a force_to_send parameter
2535 to override discarding is-info packets where nothing changed"""
2536
2537 want_pre_send = False
2538 """Whether a pre-send hook should be called when sending this packet"""
2539
2540 want_post_send = False
2541 """Whether a post-send hook should be called after sending this packet"""
2542
2543 want_post_recv = False
2544 """Wheter a post-receive hook should be called when receiving this
2545 packet"""
2546
2547 delta = True
2548 """Whether to use delta optimization for this packet"""
2549
2550 no_handle = False
2551 """Whether this packet should *not* be handled normally"""
2552
2553 handle_via_packet = True
2554 """Whether to pass the entire packet (by reference) to the handle
2555 function (rather than each field individually)"""
2556
2557 handle_per_conn = False
2558 """Whether this packet's handle function should be called with the
2559 connection instead of the attached player"""
2560
2561 def __init__(self, cfg: ScriptConfig, packet_type: str, packet_number: int, flags_text: str,
2562 lines: typing.Iterable[str], resolve_type: typing.Callable[[str], RawFieldType]):
2563 self.cfg = cfg
2564 """Configuration used when generating code for this packet"""
2565 self.type = packet_type
2566 """The packet type in allcaps (PACKET_FOO), as defined in the
2567 packet_type enum
2568
2569 See also self.name"""
2570 self.type_number = packet_number
2571 """The numeric ID of this packet type"""
2572
2573 # FIXME: Once we can use Python 3.6 features, use variable
2574 # annotations instead of empty comprehensions to set element type
2575 self.cancel = [str(_) for _ in ()]
2576 """List of packet types to drop from the cache when sending or
2577 receiving this packet type"""
2578 dirs = set()
2579
2580 for flag in flags_text.split(","):
2581 flag = flag.strip()
2582 if not flag:
2583 continue
2584
2585 if flag in ("sc", "cs"):
2586 dirs.add(flag)
2587 continue
2588 if flag == "is-info":
2589 self.is_info = "yes"
2590 continue
2591 if flag == "is-game-info":
2592 self.is_info = "game"
2593 continue
2594 if flag == "dsend":
2595 self.want_dsend = True
2596 continue
2597 if flag == "lsend":
2598 self.want_lsend = True
2599 continue
2600 if flag == "force":
2601 self.want_force = True
2602 continue
2603 if flag == "pre-send":
2604 self.want_pre_send = True
2605 continue
2606 if flag == "post-send":
2607 self.want_post_send = True
2608 continue
2609 if flag == "post-recv":
2610 self.want_post_recv = True
2611 continue
2612 if flag == "no-delta":
2613 self.delta = False
2614 continue
2615 if flag == "no-handle":
2616 self.no_handle = True
2617 continue
2618 if flag == "handle-via-fields":
2619 self.handle_via_packet = False
2620 continue
2621 if flag == "handle-per-conn":
2622 self.handle_per_conn = True
2623 continue
2624
2625 mo = __class__.CANCEL_PATTERN.fullmatch(flag)
2626 if mo is not None:
2627 self.cancel.append(mo.group(1))
2628 continue
2629
2630 raise ValueError("unrecognized flag for %s: %r" % (self.name, flag))
2631
2632 if not dirs:
2633 raise ValueError("no directions defined for %s" % self.name)
2634 self.dirs = Directions(frozenset(dirs))
2635 """Which directions this packet can be sent in"""
2636
2637 raw_fields = [
2638 field
2639 for line in lines
2640 for field in Field.parse(self.cfg, line, resolve_type)
2641 ]
2642 # put key fields before all others
2643 key_fields = [field for field in raw_fields if field.is_key]
2644 other_fields = [field for field in raw_fields if not field.is_key]
2645 self.all_fields = key_fields + other_fields
2646 """List of all fields of this packet, including name duplicates for
2647 different capability variants that are compatible.
2648
2649 Only relevant for creating Variants; self.fields should be used when
2650 not dealing with capabilities or Variants."""
2651
2652 self.fields = [Field(_, _, _, _) for _ in []]
2653 """List of all fields of this packet, with only one field of each name"""
2654 # check for duplicate field names
2655 for next_field in self.all_fields:
2656 duplicates = [field for field in self.fields if field.name == next_field.name]
2657 if not duplicates:
2658 self.fields.append(next_field)
2659 continue
2660 if not all(field.is_compatible(next_field) for field in duplicates):
2661 raise ValueError("incompatible fields with duplicate name: %s(%d).%s"
2662 % (packet_type, packet_number, next_field.name))
2663
2664 # valid, since self.fields is already set
2665 if self.no_packet:
2666 self.delta = False
2667 self.handle_via_packet = False
2668
2669 if self.want_dsend:
2670 raise ValueError("requested dsend for %s without fields isn't useful" % self.name)
2671
2672 # create cap variants
2673 all_caps = self.all_caps # valid, since self.all_fields is already set
2674 self.variants = [
2675 Variant(caps, all_caps.difference(caps), self, i + 100)
2676 for i, caps in enumerate(powerset(sorted(all_caps)))
2677 ]
2678 """List of all variants of this packet"""
2679
2680 @property
2681 def name(self) -> str:
2682 """Snake-case name of this packet type"""
2683 return self.type.lower()
2684
2685 @property
2686 def no_packet(self) -> bool:
2687 """Whether this packet's send functions should take no packet
2688 argument. This is the case iff this packet has no fields."""
2689 return not self.fields
2690
2691 @property
2692 def extra_send_args(self) -> str:
2693 """Argements for the regular send function"""
2694 return (
2695 ", const struct {self.name} *packet".format(self = self) if not self.no_packet else ""
2696 ) + (
2697 ", bool force_to_send" if self.want_force else ""
2698 )
2699
2700 @property
2701 def extra_send_args2(self) -> str:
2702 """Arguments passed from lsend to send
2703
2704 See also extra_send_args"""
2705 assert self.want_lsend
2706 return (
2707 ", packet" if not self.no_packet else ""
2708 ) + (
2709 ", force_to_send" if self.want_force else ""
2710 )
2711
2712 @property
2713 def extra_send_args3(self) -> str:
2714 """Arguments for the dsend and dlsend functions"""
2715 assert self.want_dsend
2716 return "".join(
2717 ", %s" % field.get_handle_param()
2718 for field in self.fields
2719 ) + (", bool force_to_send" if self.want_force else "")
2720
2721 @property
2722 def send_prototype(self) -> str:
2723 """Prototype for the regular send function"""
2724 return "int send_{self.name}(struct connection *pc{self.extra_send_args})".format(self = self)
2725
2726 @property
2727 def lsend_prototype(self) -> str:
2728 """Prototype for the lsend function (takes a list of connections)"""
2729 assert self.want_lsend
2730 return "void lsend_{self.name}(struct conn_list *dest{self.extra_send_args})".format(self = self)
2731
2732 @property
2733 def dsend_prototype(self) -> str:
2734 """Prototype for the dsend function (directly takes values instead of a packet struct)"""
2735 assert self.want_dsend
2736 return "int dsend_{self.name}(struct connection *pc{self.extra_send_args3})".format(self = self)
2737
2738 @property
2739 def dlsend_prototype(self) -> str:
2740 """Prototype for the dlsend function (directly takes values; list of connections)"""
2741 assert self.want_dsend
2742 assert self.want_lsend
2743 return "void dlsend_{self.name}(struct conn_list *dest{self.extra_send_args3})".format(self = self)
2744
2745 @property
2746 def all_caps(self) -> "set[str]":
2747 """Set of all capabilities affecting this packet"""
2748 return {cap for field in self.all_fields for cap in field.all_caps}
2749
2750
2751 def get_struct(self) -> str:
2752 """Generate the struct definition for this packet"""
2753 intro = """\
2754struct {self.name} {{
2755""".format(self = self)
2756 extro = """\
2757};
2758
2759"""
2760
2761 body = "".join(
2762 prefix(" ", field.get_declar())
2763 for field in self.fields
2764 ) or """\
2765 char __dummy; /* to avoid malloc(0); */
2766"""
2767 return intro+body+extro
2768
2769 def get_prototypes(self) -> str:
2770 """Generate the header prototype declarations for the public
2771 functions associated with this packet."""
2772 result = """\
2773{self.send_prototype};
2774""".format(self = self)
2775 if self.want_lsend:
2776 result += """\
2777{self.lsend_prototype};
2778""".format(self = self)
2779 if self.want_dsend:
2780 result += """\
2781{self.dsend_prototype};
2782""".format(self = self)
2783 if self.want_lsend:
2784 result += """\
2785{self.dlsend_prototype};
2786""".format(self = self)
2787 return result + "\n"
2788
2789 def get_stats(self) -> str:
2790 """Generate the code declaring counters for this packet's variants.
2791
2792 See Variant.get_stats()"""
2793 return "".join(v.get_stats() for v in self.variants)
2794
2795 def get_report_part(self) -> str:
2796 """Generate this packet's part of the delta_stats_report() function.
2797
2798 See Variant.get_report_part() and
2799 PacketsDefinition.code_delta_stats_report"""
2800 return "".join(v.get_report_part() for v in self.variants)
2801
2802 def get_reset_part(self) -> str:
2803 """Generate this packet's part of the delta_stats_reset() function.
2804
2805 See Variant.get_reset_part() and
2806 PacketsDefinition.code_delta_stats_reset"""
2807 return "\n".join(v.get_reset_part() for v in self.variants)
2808
2809 def get_send(self) -> str:
2810 """Generate the implementation of the send function, which sends a
2811 given packet to a given connection."""
2812 if self.no_packet:
2813 func="no_packet"
2814 args=""
2815 elif self.want_force:
2816 func="force_to_send"
2817 args=", packet, force_to_send"
2818 else:
2819 func="packet"
2820 args=", packet"
2821
2822 return """\
2823{self.send_prototype}
2824{{
2825 if (!pc->used) {{
2826 log_error("WARNING: trying to send data to the closed connection %s",
2827 conn_description(pc));
2828 return -1;
2829 }}
2830 fc_assert_ret_val_msg(pc->phs.handlers->send[{self.type}].{func} != NULL, -1,
2831 "Handler for {self.type} not installed");
2832 return pc->phs.handlers->send[{self.type}].{func}(pc{args});
2833}}
2834
2835""".format(self = self, func = func, args = args)
2836
2837 def get_variants(self) -> str:
2838 """Generate all code associated with individual variants of this
2839 packet; see the Variant class (and its methods) for details."""
2840 result=""
2841 for v in self.variants:
2842 if v.delta:
2843 result += """\
2844#ifdef FREECIV_DELTA_PROTOCOL
2845"""
2846 result += v.get_hash()
2847 result += v.get_cmp()
2848 result += v.get_bitvector()
2849 result += """\
2850#endif /* FREECIV_DELTA_PROTOCOL */
2851
2852"""
2853 result += v.get_receive()
2854 result += v.get_send()
2855 return result
2856
2857 def get_lsend(self) -> str:
2858 """Generate the implementation of the lsend function, which takes
2859 a list of connections to send a packet to."""
2860 if not self.want_lsend: return ""
2861 return """\
2862{self.lsend_prototype}
2863{{
2864 conn_list_iterate(dest, pconn) {{
2865 send_{self.name}(pconn{self.extra_send_args2});
2866 }} conn_list_iterate_end;
2867}}
2868
2869""".format(self = self)
2870
2871 def get_dsend(self) -> str:
2872 """Generate the implementation of the dsend function, which directly
2873 takes packet fields instead of a packet struct."""
2874 if not self.want_dsend: return ""
2875 fill = "".join(
2876 prefix(" ", field.get_fill())
2877 for field in self.fields
2878 )
2879 return """\
2880{self.dsend_prototype}
2881{{
2882 struct {self.name} packet, *real_packet = &packet;
2883
2884{fill}\
2885
2886 return send_{self.name}(pc, real_packet);
2887}}
2888
2889""".format(self = self, fill = fill)
2890
2891 def get_dlsend(self) -> str:
2892 """Generate the implementation of the dlsend function, combining
2893 dsend and lsend functionality.
2894
2895 See self.get_dsend() and self.get_lsend()"""
2896 if not (self.want_lsend and self.want_dsend): return ""
2897 fill = "".join(
2898 prefix(" ", field.get_fill())
2899 for field in self.fields
2900 )
2901 return """\
2902{self.dlsend_prototype}
2903{{
2904 struct {self.name} packet, *real_packet = &packet;
2905
2906{fill}\
2907
2908 lsend_{self.name}(dest, real_packet);
2909}}
2910
2911""".format(self = self, fill = fill)
2912
2913
2914class PacketsDefinition(typing.Iterable[Packet]):
2915 """Represents an entire packets definition file"""
2916
2917 COMMENT_START_PATTERN = re.compile(r"""
2918 ^\s* # strip initial whitespace
2919 (.*?) # actual content; note the reluctant quantifier
2920 \s* # note: this can cause quadratic backtracking
2921 (?: # match a potential comment
2922 (?: # EOL comment (or just EOL)
2923 (?:
2924 (?:\#|//) # opening # or //
2925 .*
2926 )?
2927 ) | (?: # block comment ~> capture remaining text
2928 /\* # opening /*
2929 [^*]* # text that definitely can't end the block comment
2930 (.*) # remaining text, might contain a closing */
2931 )
2932 )
2933 (?:\n)? # optional newline in case those aren't stripped
2934 $
2935 """, re.VERBOSE)
2936 """Used to clean lines when not starting inside a block comment. Finds
2937 the start of a block comment, if it exists.
2938
2939 Groups:
2940 - Actual content before any comment starts; stripped.
2941 - Remaining text after the start of a block comment. Not present if no
2942 block comment starts on this line."""
2943
2944 COMMENT_END_PATTERN = re.compile(r"""
2945 ^
2946 .*? # comment; note the reluctant quantifier
2947 (?: # end of block comment ~> capture remaining text
2948 \*/ # closing */
2949 \s* # strip whitespace after comment
2950 (.*) # remaining text
2951 )?
2952 (?:\n)? # optional newline in case those aren't stripped
2953 $
2954 """, re.VERBOSE)
2955 """Used to clean lines when starting inside a block comment. Finds the
2956 end of a block comment, if it exists.
2957
2958 Groups:
2959 - Remaining text after the end of the block comment; lstripped. Not
2960 present if the block comment doesn't end on this line."""
2961
2962 TYPE_PATTERN = re.compile(r"^\s*type\s+(\w+)\s*=\s*(.+?)\s*$")
2963 """Matches type alias definition lines
2964
2965 Groups:
2966 - the alias to define
2967 - the meaning for the alias"""
2968
2969 PACKET_HEADER_PATTERN = re.compile(r"^\s*(PACKET_\w+)\s*=\s*(\d+)\s*;\s*(.*?)\s*$")
2970 """Matches the header line of a packet definition
2971
2972 Groups:
2973 - packet type name
2974 - packet number
2975 - packet flags text"""
2976
2977 PACKET_END_PATTERN = re.compile(r"^\s*end\s*$")
2978 """Matches the "end" line terminating a packet definition"""
2979
2980 @classmethod
2981 def _clean_lines(cls, lines: typing.Iterable[str]) -> typing.Iterator[str]:
2982 """Strip comments and leading/trailing whitespace from the given
2983 lines. If a block comment starts in one line and ends in another,
2984 the remaining parts are joined together and yielded as one line."""
2985 inside_comment = False
2986 parts = []
2987
2988 for line in lines:
2989 while line:
2990 if inside_comment:
2991 # currently inside a block comment ~> look for */
2992 mo = cls.COMMENT_END_PATTERN.fullmatch(line)
2993 assert mo, repr(line)
2994 # If the group wasn't captured (None), we haven't found
2995 # a */ to end our comment ~> still inside_comment
2996 # Otherwise, group captured remaining line content
2997 line, = mo.groups(None)
2998 inside_comment = line is None
2999 else:
3000 mo = cls.COMMENT_START_PATTERN.fullmatch(line)
3001 assert mo, repr(line)
3002 # If the second group wasn't captured (None), there is
3003 # no /* to start a block comment ~> not inside_comment
3004 part, line = mo.groups(None)
3005 inside_comment = line is not None
3006 if part: parts.append(part)
3007
3008 if (not inside_comment) and parts:
3009 # when ending a line outside a block comment, yield what
3010 # we've accumulated
3011 yield " ".join(parts)
3012 parts.clear()
3013
3014 if inside_comment:
3015 raise ValueError("EOF while scanning block comment")
3016
3017 def parse_lines(self, lines: typing.Iterable[str]):
3018 """Parse the given lines as type and packet definitions."""
3019 self.parse_clean_lines(self._clean_lines(lines))
3020
3021 def parse_clean_lines(self, lines: typing.Iterable[str]):
3022 """Parse the given lines as type and packet definitions. Comments
3023 and blank lines must already be removed beforehand."""
3024 # hold on to the iterator itself
3025 lines_iter = iter(lines)
3026 for line in lines_iter:
3027 mo = self.TYPE_PATTERN.fullmatch(line)
3028 if mo is not None:
3029 self.define_type(*mo.groups())
3030 continue
3031
3032 mo = self.PACKET_HEADER_PATTERN.fullmatch(line)
3033 if mo is not None:
3034 packet_type, packet_number, flags_text = mo.groups("")
3035 packet_number = int(packet_number)
3036
3037 if packet_type in self.packets_by_type:
3038 raise ValueError("Duplicate packet type: " + packet_type)
3039
3040 if packet_number not in range(65536):
3041 raise ValueError("packet number %d for %s outside legal range [0,65536)" % (packet_number, packet_type))
3042 if packet_number in self.packets_by_number:
3043 raise ValueError("Duplicate packet number: %d (%s and %s)" % (
3044 packet_number,
3045 self.packets_by_number[packet_number].type,
3046 packet_type,
3047 ))
3048
3049 packet = Packet(
3050 self.cfg, packet_type, packet_number, flags_text,
3051 takewhile(
3052 lambda line: self.PACKET_END_PATTERN.fullmatch(line) is None,
3053 lines_iter, # advance the iterator used by this for loop
3054 ),
3055 self.resolve_type,
3056 )
3057
3058 self.packets.append(packet)
3059 self.packets_by_number[packet_number] = packet
3060 self.packets_by_type[packet_type] = packet
3061 self.packets_by_dirs[packet.dirs].append(packet)
3062 continue
3063
3064 raise ValueError("Unexpected line: " + line)
3065
3066 def resolve_type(self, type_text: str) -> RawFieldType:
3067 """Resolve the given type"""
3068 if type_text not in self.types:
3069 self.types[type_text] = self.type_registry.parse(type_text)
3070 return self.types[type_text]
3071
3072 def define_type(self, alias: str, meaning: str):
3073 """Define a type alias"""
3074 if alias in self.types:
3075 if meaning == self.types[alias]:
3076 self.cfg.log_verbose("duplicate typedef: %r = %r" % (alias, meaning))
3077 return
3078 else:
3079 raise ValueError("duplicate type alias %r: %r and %r"
3080 % (alias, self.types[alias], meaning))
3081
3082 self.types[alias] = self.resolve_type(meaning)
3083
3084 def __init__(self, cfg: ScriptConfig, type_registry: "TypeRegistry | None" = None):
3085 self.cfg = cfg
3086 """Configuration used for code generated from this definition"""
3087 self.type_registry = type_registry or DEFAULT_REGISTRY
3088 """Type registry used to resolve type classes for field types"""
3089 # FIXME: Once we can use Python 3.6 features, use variable
3090 # annotations instead of empty comprehensions to set element type
3091 self.types = {
3092 str(_): self.type_registry(_, _)
3093 for _ in ()
3094 }
3095 """Maps type aliases and definitions to the parsed type"""
3096 self.packets = [
3097 Packet(*[_])
3098 for _ in ()
3099 ]
3100 """List of all packets, in order of definition"""
3101 self.packets_by_type = {
3102 str(_): self.packets[_]
3103 for _ in ()
3104 }
3105 """Maps packet types (PACKET_FOO) to the packet with that type"""
3106 self.packets_by_number = {
3107 int(_): self.packets[_]
3108 for _ in ()
3109 }
3110 """Maps packet IDs to the packet with that ID"""
3111 self.packets_by_dirs = {
3112 dirs: [
3113 self.packets[_]
3114 for _ in ()
3115 ]
3116 for dirs in Directions
3117 }
3118 """Maps packet directions to lists of packets with those
3119 directions, in order of definition"""
3120
3121 def __iter__(self) -> typing.Iterator[Packet]:
3122 return iter(self.packets)
3123
3124 def iter_by_number(self) -> "typing.Generator[tuple[int, Packet, int], None, int]":
3125 """Yield (number, packet, skipped) tuples in order of packet number.
3126
3127 skipped is how many numbers were skipped since the last packet
3128
3129 Return the maximum packet number (or -1 if there are no packets)
3130 when used with `yield from`."""
3131 last = -1
3132 for n, packet in sorted(self.packets_by_number.items()):
3133 assert n == packet.type_number
3134 yield (n, packet, n - last - 1)
3135 last = n
3136 return last
3137
3138 @property
3139 def all_caps(self) -> "set[str]":
3140 """Set of all capabilities affecting the defined packets"""
3141 return set().union(*(p.all_caps for p in self))
3142
3143 @property
3144 def code_packet_functional_capability(self) -> str:
3145 """Code fragment defining the packet_functional_capability string"""
3146 return """\
3147
3148const char *const packet_functional_capability = "%s";
3149""" % " ".join(sorted(self.all_caps))
3150
3151 @property
3152 def code_delta_stats_report(self) -> str:
3153 """Code fragment implementing the delta_stats_report() function"""
3154 if not self.cfg.gen_stats: return """\
3155void delta_stats_report(void) {}
3156
3157"""
3158
3159 intro = """\
3160void delta_stats_report(void) {
3161 int i;
3162"""
3163 extro = """\
3164}
3165
3166"""
3167 body = "".join(
3168 prefix(" ", packet.get_report_part())
3169 for packet in self
3170 )
3171 return intro + body + extro
3172
3173 @property
3174 def code_delta_stats_reset(self) -> str:
3175 """Code fragment implementing the delta_stats_reset() function"""
3176 if not self.cfg.gen_stats: return """\
3177void delta_stats_reset(void) {}
3178
3179"""
3180
3181 intro = """\
3182void delta_stats_reset(void) {
3183"""
3184 extro = """\
3185}
3186
3187"""
3188 body = "\n".join(
3189 prefix(" ", packet.get_reset_part())
3190 for packet in self
3191 )
3192 return intro + body + extro
3193
3194 @property
3195 def code_packet_name(self) -> str:
3196 """Code fragment implementing the packet_name() function"""
3197 intro = """\
3198const char *packet_name(enum packet_type type)
3199{
3200 static const char *const names[PACKET_LAST] = {
3201"""
3202
3203 body = ""
3204 for _, packet, skipped in self.iter_by_number():
3205 body += """\
3206 "unknown",
3207""" * skipped
3208 body += """\
3209 "%s",
3210""" % packet.type
3211
3212 extro = """\
3213 };
3214
3215 return (type < PACKET_LAST ? names[type] : "unknown");
3216}
3217
3218"""
3219 return intro + body + extro
3220
3221 @property
3222 def code_packet_has_game_info_flag(self) -> str:
3223 """Code fragment implementing the packet_has_game_info_flag()
3224 function"""
3225 intro = """\
3226bool packet_has_game_info_flag(enum packet_type type)
3227{
3228 static const bool flag[PACKET_LAST] = {
3229"""
3230 body = ""
3231 for _, packet, skipped in self.iter_by_number():
3232 body += """\
3233 FALSE,
3234""" * skipped
3235 if packet.is_info != "game":
3236 body += """\
3237 FALSE, /* %s */
3238""" % packet.type
3239 else:
3240 body += """\
3241 TRUE, /* %s */
3242""" % packet.type
3243
3244 extro = """\
3245 };
3246
3247 return (type < PACKET_LAST ? flag[type] : FALSE);
3248}
3249
3250"""
3251 return intro + body + extro
3252
3253 @property
3254 def code_packet_handlers_fill_initial(self) -> str:
3255 """Code fragment implementing the packet_handlers_fill_initial()
3256 function"""
3257 intro = """\
3258void packet_handlers_fill_initial(struct packet_handlers *phandlers)
3259{
3260"""
3261 for cap in sorted(self.all_caps):
3262 intro += """\
3263 fc_assert_msg(has_capability("{0}", our_capability),
3264 "Packets have support for unknown '{0}' capability!");
3265""".format(cap)
3266
3267 down_only = [
3268 packet.variants[0]
3269 for packet in self.packets_by_dirs[Directions.DOWN_ONLY]
3270 if len(packet.variants) == 1
3271 ]
3272 up_only = [
3273 packet.variants[0]
3274 for packet in self.packets_by_dirs[Directions.UP_ONLY]
3275 if len(packet.variants) == 1
3276 ]
3277 unrestricted = [
3278 packet.variants[0]
3279 for packet in self.packets_by_dirs[Directions.UNRESTRICTED]
3280 if len(packet.variants) == 1
3281 ]
3282
3283 body = ""
3284 for variant in unrestricted:
3285 body += prefix(" ", variant.send_handler)
3286 body += prefix(" ", variant.receive_handler)
3287 body += """\
3288 if (is_server()) {
3289"""
3290 for variant in down_only:
3291 body += prefix(" ", variant.send_handler)
3292 for variant in up_only:
3293 body += prefix(" ", variant.receive_handler)
3294 body += """\
3295 } else {
3296"""
3297 for variant in up_only:
3298 body += prefix(" ", variant.send_handler)
3299 for variant in down_only:
3300 body += prefix(" ", variant.receive_handler)
3301
3302 extro = """\
3303 }
3304}
3305
3306"""
3307 return intro + body + extro
3308
3309 @property
3310 def code_packet_handlers_fill_capability(self) -> str:
3311 """Code fragment implementing the packet_handlers_fill_capability()
3312 function"""
3313 intro = """\
3314void packet_handlers_fill_capability(struct packet_handlers *phandlers,
3315 const char *capability)
3316{
3317"""
3318
3319 down_only = [
3320 packet
3321 for packet in self.packets_by_dirs[Directions.DOWN_ONLY]
3322 if len(packet.variants) > 1
3323 ]
3324 up_only = [
3325 packet
3326 for packet in self.packets_by_dirs[Directions.UP_ONLY]
3327 if len(packet.variants) > 1
3328 ]
3329 unrestricted = [
3330 packet
3331 for packet in self.packets_by_dirs[Directions.UNRESTRICTED]
3332 if len(packet.variants) > 1
3333 ]
3334
3335 body = ""
3336 for p in unrestricted:
3337 body += " "
3338 for v in p.variants:
3339 hand = prefix(" ", v.send_handler + v.receive_handler)
3340 body += """if ({v.condition}) {{
3341 {v.log_macro}("{v.type}: using variant={v.no} cap=%s", capability);
3342{hand}\
3343 }} else """.format(v = v, hand = hand)
3344 body += """{{
3345 log_error("Unknown {p.type} variant for cap %s", capability);
3346 }}
3347""".format(p = p)
3348 if up_only or down_only:
3349 body += """\
3350 if (is_server()) {
3351"""
3352 for p in down_only:
3353 body += " "
3354 for v in p.variants:
3355 hand = prefix(" ", v.send_handler)
3356 body += """if ({v.condition}) {{
3357 {v.log_macro}("{v.type}: using variant={v.no} cap=%s", capability);
3358{hand}\
3359 }} else """.format(v = v, hand = hand)
3360 body += """{{
3361 log_error("Unknown {p.type} variant for cap %s", capability);
3362 }}
3363""".format(p = p)
3364 for p in up_only:
3365 body += " "
3366 for v in p.variants:
3367 hand = prefix(" ", v.receive_handler)
3368 body += """if ({v.condition}) {{
3369 {v.log_macro}("{v.type}: using variant={v.no} cap=%s", capability);
3370{hand}\
3371 }} else """.format(v = v, hand = hand)
3372 body += """{{
3373 log_error("Unknown {p.type} variant for cap %s", capability);
3374 }}
3375""".format(p = p)
3376 body += """\
3377 } else {
3378"""
3379 for p in up_only:
3380 body += " "
3381 for v in p.variants:
3382 hand = prefix(" ", v.send_handler)
3383 body += """if ({v.condition}) {{
3384 {v.log_macro}("{v.type}: using variant={v.no} cap=%s", capability);
3385{hand}\
3386 }} else """.format(v = v, hand = hand)
3387 body += """{{
3388 log_error("Unknown {p.type} variant for cap %s", capability);
3389 }}
3390""".format(p = p)
3391 for p in down_only:
3392 body += " "
3393 for v in p.variants:
3394 hand = prefix(" ", v.receive_handler)
3395 body += """if ({v.condition}) {{
3396 {v.log_macro}("{v.type}: using variant={v.no} cap=%s", capability);
3397{hand}\
3398 }} else """.format(v = v, hand = hand)
3399 body += """{{
3400 log_error("Unknown {p.type} variant for cap %s", capability);
3401 }}
3402""".format(p = p)
3403 body += """\
3404 }
3405"""
3406
3407 extro = """\
3408}
3409"""
3410 return intro + body + extro
3411
3412 @property
3413 def code_enum_packet(self) -> str:
3414 """Code fragment declaring the packet_type enum"""
3415 intro = """\
3416enum packet_type {
3417"""
3418 body = ""
3419 for n, packet, skipped in self.iter_by_number():
3420 if skipped:
3421 line = " %s = %d," % (packet.type, n)
3422 else:
3423 line = " %s," % (packet.type)
3424
3425 if not (n % 10):
3426 line = "%-40s /* %d */" % (line, n)
3427 body += line + "\n"
3428
3429 extro = """\
3430
3431 PACKET_LAST /* leave this last */
3432};
3433
3434"""
3435 return intro + body + extro
3436
3437
3438########################### Writing output files ###########################
3439
3440def write_common_header(path: "str | Path | None", packets: PacketsDefinition):
3441 """Write contents for common/packets_gen.h to the given path"""
3442 if path is None:
3443 return
3444 with packets.cfg.open_write(path, wrap_header = "packets_gen") as output_h:
3445 output_h.write("""\
3446/* common */
3447#include "actions.h"
3448#include "city.h"
3449#include "disaster.h"
3450#include "unit.h"
3451
3452/* common/aicore */
3453#include "cm.h"
3454
3455""")
3456
3457 # write structs
3458 for p in packets:
3459 output_h.write(p.get_struct())
3460
3461 output_h.write(packets.code_enum_packet)
3462
3463 # write function prototypes
3464 for p in packets:
3465 output_h.write(p.get_prototypes())
3466 output_h.write("""\
3467void delta_stats_report(void);
3468void delta_stats_reset(void);
3469""")
3470
3471def write_common_impl(path: "str | Path | None", packets: PacketsDefinition):
3472 """Write contents for common/packets_gen.c to the given path"""
3473 if path is None:
3474 return
3475 with packets.cfg.open_write(path) as output_c:
3476 output_c.write("""\
3477#ifdef HAVE_CONFIG_H
3478#include <fc_config.h>
3479#endif
3480
3481#include <string.h>
3482
3483/* utility */
3484#include "bitvector.h"
3485#include "capability.h"
3486#include "genhash.h"
3487#include "log.h"
3488#include "mem.h"
3489#include "support.h"
3490
3491/* common */
3492#include "capstr.h"
3493#include "connection.h"
3494#include "dataio.h"
3495#include "game.h"
3496
3497#include "packets.h"
3498""")
3499 output_c.write(packets.code_packet_functional_capability)
3500 output_c.write("""\
3501
3502#ifdef FREECIV_DELTA_PROTOCOL
3503static genhash_val_t hash_const(const void *vkey)
3504{
3505 return 0;
3506}
3507
3508static bool cmp_const(const void *vkey1, const void *vkey2)
3509{
3510 return TRUE;
3511}
3512#endif /* FREECIV_DELTA_PROTOCOL */
3513
3514""")
3515
3516 if packets.cfg.gen_stats:
3517 output_c.write("""\
3518static int stats_total_sent;
3519
3520""")
3521 # write stats
3522 for p in packets:
3523 output_c.write(p.get_stats())
3524 # write report()
3525 output_c.write(packets.code_delta_stats_report)
3526 output_c.write(packets.code_delta_stats_reset)
3527
3528 output_c.write(packets.code_packet_name)
3529 output_c.write(packets.code_packet_has_game_info_flag)
3530
3531 # write hash, cmp, send, receive
3532 for p in packets:
3533 output_c.write(p.get_variants())
3534 output_c.write(p.get_send())
3535 output_c.write(p.get_lsend())
3536 output_c.write(p.get_dsend())
3537 output_c.write(p.get_dlsend())
3538
3539 output_c.write(packets.code_packet_handlers_fill_initial)
3540 output_c.write(packets.code_packet_handlers_fill_capability)
3541
3542def write_server_header(path: "str | Path | None", packets: PacketsDefinition):
3543 """Write contents for server/hand_gen.h to the given path"""
3544 if path is None:
3545 return
3546 with packets.cfg.open_write(path, wrap_header = "hand_gen", cplusplus = False) as f:
3547 f.write("""\
3548/* utility */
3549#include "shared.h"
3550
3551/* common */
3552#include "fc_types.h"
3553#include "packets.h"
3554
3555struct connection;
3556
3557bool server_handle_packet(enum packet_type type, const void *packet,
3558 struct player *pplayer, struct connection *pconn);
3559
3560""")
3561
3562 for p in packets:
3563 if p.dirs.up and not p.no_handle:
3564 a=p.name[len("packet_"):]
3565 b = "".join(
3566 ", %s" % field.get_handle_param()
3567 for field in p.fields
3568 )
3569 if p.handle_per_conn:
3570 sender = "struct connection *pc"
3571 else:
3572 sender = "struct player *pplayer"
3573 if p.handle_via_packet:
3574 f.write("""\
3575struct %s;
3576void handle_%s(%s, const struct %s *packet);
3577""" % (p.name, a, sender, p.name))
3578 else:
3579 f.write("""\
3580void handle_%s(%s%s);
3581""" % (a, sender, b))
3582
3583def write_client_header(path: "str | Path | None", packets: PacketsDefinition):
3584 """Write contents for client/packhand_gen.h to the given path"""
3585 if path is None:
3586 return
3587 with packets.cfg.open_write(path, wrap_header = "packhand_gen") as f:
3588 f.write("""\
3589/* utility */
3590#include "shared.h"
3591
3592/* common */
3593#include "packets.h"
3594
3595bool client_handle_packet(enum packet_type type, const void *packet);
3596
3597""")
3598 for p in packets:
3599 if not p.dirs.down: continue
3600
3601 a=p.name[len("packet_"):]
3602 b = ", ".join(
3603 field.get_handle_param()
3604 for field in p.fields
3605 ) or "void"
3606 if p.handle_via_packet:
3607 f.write("""\
3608struct %s;
3609void handle_%s(const struct %s *packet);
3610""" % (p.name, a, p.name))
3611 else:
3612 f.write("""\
3613void handle_%s(%s);
3614""" % (a, b))
3615
3616def write_server_impl(path: "str | Path | None", packets: PacketsDefinition):
3617 """Write contents for server/hand_gen.c to the given path"""
3618 if path is None:
3619 return
3620 with packets.cfg.open_write(path) as f:
3621 f.write("""\
3622#ifdef HAVE_CONFIG_H
3623#include <fc_config.h>
3624#endif
3625
3626/* common */
3627#include "packets.h"
3628
3629#include "hand_gen.h"
3630
3631bool server_handle_packet(enum packet_type type, const void *packet,
3632 struct player *pplayer, struct connection *pconn)
3633{
3634 switch (type) {
3635""")
3636 for p in packets:
3637 if not p.dirs.up: continue
3638 if p.no_handle: continue
3639 a=p.name[len("packet_"):]
3640
3641 if p.handle_via_packet:
3642 args = ", packet"
3643
3644 else:
3645 packet_arrow = "((const struct %s *)packet)->" % p.name
3646 args = "".join(
3647 ",\n " + field.get_handle_arg(packet_arrow)
3648 for field in p.fields
3649 )
3650
3651 if p.handle_per_conn:
3652 first_arg = "pconn"
3653 else:
3654 first_arg = "pplayer"
3655
3656 f.write("""\
3657 case %s:
3658 handle_%s(%s%s);
3659 return TRUE;
3660
3661""" % (p.type, a, first_arg, args))
3662 f.write("""\
3663 default:
3664 return FALSE;
3665 }
3666}
3667""")
3668
3669def write_client_impl(path: "str | Path | None", packets: PacketsDefinition):
3670 """Write contents for client/packhand_gen.c to the given path"""
3671 if path is None:
3672 return
3673 with packets.cfg.open_write(path) as f:
3674 f.write("""\
3675#ifdef HAVE_CONFIG_H
3676#include <fc_config.h>
3677#endif
3678
3679/* common */
3680#include "packets.h"
3681
3682#include "packhand_gen.h"
3683
3684bool client_handle_packet(enum packet_type type, const void *packet)
3685{
3686 switch (type) {
3687""")
3688 for p in packets:
3689 if not p.dirs.down: continue
3690 if p.no_handle: continue
3691 a=p.name[len("packet_"):]
3692
3693 if p.handle_via_packet:
3694 args="packet"
3695 else:
3696 packet_arrow = "((const struct %s *)packet)->" % p.name
3697 args = "".join(
3698 ",\n " + field.get_handle_arg(packet_arrow)
3699 for field in p.fields
3700 )[1:] # cut off initial comma
3701
3702 f.write("""\
3703 case %s:
3704 handle_%s(%s);
3705 return TRUE;
3706
3707""" % (p.type, a, args))
3708 f.write("""\
3709 default:
3710 return FALSE;
3711 }
3712}
3713""")
3714
3715
3716def main(raw_args: "typing.Sequence[str] | None" = None):
3717 """Main function. Read the given arguments, or the command line
3718 arguments if raw_args is not given, and run the packet code generation
3719 script accordingly."""
3720 script_args = ScriptConfig(raw_args)
3721
3722 packets = PacketsDefinition(script_args)
3723 for path in script_args.def_paths:
3724 with path.open() as input_file:
3725 packets.parse_lines(input_file)
3726
3727 write_common_header(script_args.common_header_path, packets)
3728 write_common_impl(script_args.common_impl_path, packets)
3729 write_server_header(script_args.server_header_path, packets)
3730 write_client_header(script_args.client_header_path, packets)
3731 write_server_impl(script_args.server_impl_path, packets)
3732 write_client_impl(script_args.client_impl_path, packets)
3733
3734
3735if __name__ == "__main__":
3736 main()
__init__(self, "typing.Sequence[str] | None" args=None)
argparse.ArgumentParser get_argparser()
char * incite_cost
Definition comments.c:75
Path file_path("str | Path" s)
Parsing Command Line Arguments ######################.