Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3Classes that define C constructs.
4"""
6# TODO: make a static default for LiteralFormatters for using as default, instead
7# of using another instance every time.
8# TODO: add typedefs, especially for function pointers
9import collections
10from abc import ABCMeta
11from abc import abstractmethod
12from collections import deque
13from collections import namedtuple
14from itertools import chain
15from typing import Callable
16from typing import Collection
17from typing import Iterable
18from typing import Iterator
19from typing import List
20from typing import Mapping
21from typing import Optional
22from typing import Tuple
23from typing import Union
25from .codewriterlite import CodeWriterLite
26from .utils import assure_str
27from .utils import seq_get
29# ruff: noqa: B024
32# internal types used by csnake as initialization values
33class CIntLiteral(metaclass=ABCMeta):
34 """ABC for all C integer literals."""
37CIntLiteral.register(int)
40class CFloatLiteral(metaclass=ABCMeta):
41 """ABC for all C floating point literals."""
44CFloatLiteral.register(float)
47class CArrayLiteral(metaclass=ABCMeta):
48 """ABC for array literals: sized Iterables except str, Mapping."""
50 @classmethod
51 def __subclasshook__(cls, subclass):
52 if cls is CArrayLiteral:
53 if (
54 issubclass(subclass, collections.abc.Sized)
55 and issubclass(subclass, collections.abc.Iterable)
56 and not issubclass(subclass, (str, collections.abc.Mapping))
57 ):
58 return True
59 return False
60 return NotImplemented
63CArrayLiteral.register(list)
64CArrayLiteral.register(tuple)
67class CStructLiteral(metaclass=ABCMeta):
68 """ABC for struct literals: any Mapping (dict-like)."""
70 @classmethod
71 def __subclasshook__(cls, subclass):
72 if cls is CStructLiteral:
73 return issubclass(subclass, collections.abc.Mapping)
74 return NotImplemented
77CStructLiteral.register(dict)
80class CBasicLiteral(metaclass=ABCMeta):
81 """ABC for literals of basic C types (int, float)."""
84CBasicLiteral.register(CIntLiteral)
85CBasicLiteral.register(CFloatLiteral)
88class CExtendedLiteral(metaclass=ABCMeta):
89 """ABC for basic C literals (int, float) + bool and char[]."""
92CExtendedLiteral.register(CBasicLiteral)
93CExtendedLiteral.register(bool)
94CExtendedLiteral.register(str)
97class CLiteral(metaclass=ABCMeta):
98 """ABC for any C literal."""
100 @classmethod
101 def __subclasshook__(cls, subclass):
102 if cls is CLiteral:
103 return issubclass(
104 subclass, (CExtendedLiteral, CStructLiteral, CArrayLiteral)
105 )
106 return NotImplemented
109CLiteral.register(CExtendedLiteral)
110CLiteral.register(CStructLiteral)
112# conditional imports
113try:
114 import numpy as np
115except ImportError:
116 pass
117else:
118 CIntLiteral.register(np.integer)
119 CFloatLiteral.register(np.floating)
121try:
122 import sympy as sp
123except ImportError:
124 pass
125else:
126 CIntLiteral.register(sp.Integer)
127 CFloatLiteral.register(sp.Float)
130class ShapelessError(TypeError):
131 """Argument doesn't have a shape."""
134def _shape(array: Iterable) -> tuple:
135 """Return dimensions (shape) of a multidimensional list."""
137 try:
138 return array.shape # type: ignore
139 except AttributeError:
140 pass
142 if isinstance(array, str):
143 raise ShapelessError("Strings don't have a shape.")
145 if isinstance(array, Mapping):
146 raise ShapelessError("Mappings (like dicts) don't have a shape.")
148 curr = array
149 shp: List[int] = []
151 while True:
152 if not isinstance(curr, CArrayLiteral):
153 return tuple(shp)
154 try:
155 shp.append(len(curr))
156 itr = iter(curr)
157 curr = next(itr)
158 except (TypeError, IndexError):
159 return tuple(shp)
162# C constructs:
165def simple_bool_formatter(value: bool) -> str:
166 """Simple formatter that turns a bool into 'true' or 'false'.
168 Args:
169 value: boolean to format
171 Returns:
172 :obj:`str`'s 'true' or 'false'
173 """
174 if value:
175 return "true"
176 return "false"
179def simple_str_formatter(value: str) -> str:
180 """Simple formatter that surrounds a str with quotes.
182 Args:
183 value: string to format
185 Returns:
186 :obj:`str` that's the input string surrounded with double quotes
187 """
188 return f'"{value}"'
191class LiteralFormatters:
192 """Collection of formatters used for formatting literals.
194 Any values left out from constructor call is set to defaults.
196 Args:
197 int_formatter: function to call when formatting :ccode:`int` literals
198 float_formatter: function to call when formatting :ccode:`float` literals
199 bool_formatter: function to call when formatting :ccode:`bool` literals
200 string_formatter: function to call when formatting :ccode:`char[]` literals
202 Attributes:
203 int_formatter: function to call when formatting :ccode:`int` literals
204 float_formatter: function to call when formatting :ccode:`float` literals
205 bool_formatter: function to call when formatting :ccode:`bool` literals
206 string_formatter: function to call when formatting :ccode:`char[]` literals
207 """
209 __slots__ = (
210 "int_formatter",
211 "float_formatter",
212 "string_formatter",
213 "bool_formatter",
214 )
216 def __init__(
217 self,
218 int_formatter: Callable = int,
219 float_formatter: Callable = float,
220 bool_formatter: Callable = simple_bool_formatter,
221 string_formatter: Callable = simple_str_formatter,
222 ) -> None:
223 self.int_formatter = int_formatter
224 self.float_formatter = float_formatter
225 self.bool_formatter = bool_formatter
226 self.string_formatter = string_formatter
228 @classmethod
229 def empty(cls):
230 """Returns an empty :class: instance (`None` for every parameter)."""
231 return cls(**{key: None for key in cls.__slots__})
233 def __repr__(self):
234 """Simple printout of parameter values."""
235 lines = (f" {slot}: {getattr(self, slot)}" for slot in self.__slots__)
236 return "formatters {\n" + "\n".join(lines) + "\n}"
238 def replace(self, *args, **kwargs):
239 """Return a copy of collection with certain formatters replaced."""
241 if len(args) == 1 and not kwargs:
242 arg = next(iter(args))
243 if isinstance(arg, LiteralFormatters):
244 initializer = {
245 slot: getattr(arg, slot)
246 if getattr(arg, slot) is not None
247 else getattr(self, slot)
248 for slot in self.__slots__
249 }
251 return LiteralFormatters(**initializer)
253 if isinstance(arg, Mapping):
254 return self.replace(**arg)
256 if kwargs and not args:
257 diff = set(kwargs.keys()) - set(self.__slots__)
258 if diff:
259 raise TypeError(
260 f"Arguments {diff!s} are invalid for this function"
261 )
263 initializer = {
264 slot: kwargs[slot] if slot in kwargs else getattr(self, slot)
265 for slot in self.__slots__
266 }
268 return LiteralFormatters(**initializer)
270 raise TypeError("Invalid arguments for this function")
272 @classmethod
273 def partial(cls, dictionary: Mapping):
274 """Create a partial formatter collection based on a dict."""
275 rtnval = cls.empty()
276 return rtnval.replace(dictionary)
279class FormattedLiteral:
280 """A C literal with its formatter collection altered.
282 The accompanying literal formatter collection will be used when generating
283 the initialization :obj:`str`.
285 Args:
286 \\*\\*kwargs: keyword arguments for each of the formatters in
287 ['int_formatter', 'float_formatter', 'bool_formatter',
288 'string_formatter'] we want to change for this literal. Every
289 missing formatter is inherited from the literal (or default).
291 Attributes:
292 value: value of literal
293 formatter: collection of literal formatters
294 """
296 __slots__ = ("value", "formatter")
298 def __init__(self, value: CLiteral, *args, **kwargs) -> None:
299 self.value = value
300 if args:
301 raise ValueError(f"Unexpected arguments: {args}")
303 self.formatter = LiteralFormatters.partial(kwargs)
306class VariableValue(metaclass=ABCMeta):
307 """Abstract base class for any initializer value based on a variable.
309 Sometimes we want to initialize a value to another variable, but in some
310 more complicated manner: using the address-of operator, dereference
311 operator, subscripting, typecasting... This is an ABC for those
312 modifiers.
313 """
315 @abstractmethod
316 def init_target_code(self, formatters: Optional[LiteralFormatters] = None):
317 """Return code used for variable initialization, formatted with the
318 supplied formatters.
320 Args:
321 formatters: collection of formatters used for formatting the
322 initialization :obj:`str`
323 """
325 @abstractmethod
326 def __str__(self):
327 """Return :obj:`str` containing initialization code."""
330def generate_c_value_initializer(
331 cval: Union[CExtendedLiteral, VariableValue],
332 formatters: Optional[LiteralFormatters] = None,
333) -> str:
334 """Generate an initialization str from a literal using formatters."""
335 if formatters is None:
336 formatters = LiteralFormatters()
338 if isinstance(cval, VariableValue):
339 return str(cval.init_target_code(formatters))
340 if isinstance(cval, bool):
341 return str(formatters.bool_formatter(cval))
342 if isinstance(cval, CIntLiteral):
343 return str(formatters.int_formatter(cval))
344 if isinstance(cval, CFloatLiteral):
345 return str(formatters.float_formatter(cval))
346 if isinstance(cval, str):
347 return str(formatters.string_formatter(cval))
349 raise TypeError("cval must be a CExtendedLiteral.")
352class _ArrStructInitGen:
353 """Classes and functions that help with initialization generation."""
355 __slots__ = ("type_action_pairs",)
357 def __registration(self):
358 "Fake function to register slots with mypy. Not meant to be called."
359 self.type_action_pairs = None
360 raise NotImplementedError
362 class OpenBrace:
363 """Helper class to identify open braces while printing."""
365 __slots__ = ()
367 class ClosedBrace:
368 """Helper class to identify closed braces while printing.
370 struct_closing means the brace closes a struct initialization
371 """
373 __slots__ = ("struct_closing",)
375 def __init__(self, struct_closing: bool = False) -> None:
376 self.struct_closing = bool(struct_closing)
378 class Designator:
379 """Helper class to identify struct designators."""
381 __slots__ = ("name",)
383 def __init__(self, name: str) -> None:
384 self.name = name
386 @staticmethod
387 def lookthrough_ignoring_format(
388 forvar: Union[FormattedLiteral, CLiteral],
389 ) -> Union[CLiteral, FormattedLiteral]:
390 if isinstance(forvar, FormattedLiteral):
391 return _ArrStructInitGen.lookthrough_ignoring_format(forvar.value)
392 return forvar
394 @staticmethod
395 def lookahead_ignoring_format(
396 stack: deque,
397 ) -> Union[
398 Designator,
399 OpenBrace,
400 ClosedBrace,
401 CArrayLiteral,
402 CStructLiteral,
403 CExtendedLiteral,
404 VariableValue,
405 ]:
406 index = -1
407 while True:
408 try:
409 lookahead = stack[index]
410 except IndexError as e:
411 raise IndexError(
412 "no next element of interest on stack."
413 ) from e
415 if isinstance(lookahead, FormattedLiteral):
416 res = _ArrStructInitGen.lookthrough_ignoring_format(lookahead)
417 assert isinstance(res, CLiteral)
418 return res # type: ignore
420 if isinstance(lookahead, LiteralFormatters):
421 index -= 1
422 continue
424 return lookahead
426 @staticmethod
427 def c_array_literal_handler(
428 top,
429 stack: deque,
430 writer: CodeWriterLite,
431 formatters: LiteralFormatters,
432 ) -> LiteralFormatters:
433 stack.append(_ArrStructInitGen.ClosedBrace())
434 try:
435 stack.extend(reversed(top)) # type: ignore
436 except TypeError:
437 tempdeq: deque = deque()
438 assert isinstance(top, Iterable)
439 tempdeq.extendleft(top)
440 stack.extend(tempdeq)
441 stack.append(_ArrStructInitGen.OpenBrace())
442 return formatters
444 @staticmethod
445 def c_struct_literal_handler(
446 top,
447 stack: deque,
448 writer: CodeWriterLite,
449 formatters: LiteralFormatters,
450 ) -> LiteralFormatters:
451 stack.append(_ArrStructInitGen.ClosedBrace(struct_closing=True))
452 assert isinstance(top, Mapping)
453 dict_pairs = (
454 (value, _ArrStructInitGen.Designator(key))
455 for key, value in reversed(list(top.items()))
456 )
457 flatdict = (item for sublist in dict_pairs for item in sublist)
458 stack.extend(flatdict)
459 stack.append(_ArrStructInitGen.OpenBrace())
460 return formatters
462 @staticmethod
463 def closed_brace_handler(
464 top,
465 stack: deque,
466 writer: CodeWriterLite,
467 formatters: LiteralFormatters,
468 ) -> LiteralFormatters:
469 writer.dedent()
470 if top.struct_closing:
471 writer.add_line("}")
472 else:
473 writer.add("}")
475 try:
476 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack)
477 except IndexError:
478 pass
479 else:
480 if isinstance(lookahead, _ArrStructInitGen.ClosedBrace):
481 writer.dedent()
482 writer.add_line("")
483 writer.indent()
484 elif isinstance(
485 lookahead, (CArrayLiteral, _ArrStructInitGen.OpenBrace)
486 ):
487 writer.add(",")
488 writer.add_line("")
489 else:
490 writer.add(",")
491 return formatters
493 @staticmethod
494 def open_brace_handler(
495 top,
496 stack: deque,
497 writer: CodeWriterLite,
498 formatters: LiteralFormatters,
499 ) -> LiteralFormatters:
500 writer.add("{")
501 writer.indent()
503 try:
504 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack)
505 except IndexError:
506 pass
507 else:
508 if isinstance(
509 lookahead,
510 (_ArrStructInitGen.OpenBrace, CArrayLiteral, CStructLiteral),
511 ):
512 writer.add_line("")
513 return formatters
515 @staticmethod
516 def literal_or_value_handler(
517 top,
518 stack: deque,
519 writer: CodeWriterLite,
520 formatters: LiteralFormatters,
521 ) -> LiteralFormatters:
522 writer.add(generate_c_value_initializer(top, formatters))
524 try:
525 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack)
526 except IndexError:
527 pass
528 else:
529 if isinstance(
530 lookahead,
531 (
532 CExtendedLiteral,
533 VariableValue,
534 _ArrStructInitGen.Designator,
535 ),
536 ):
537 writer.add(",")
538 if isinstance(lookahead, (CExtendedLiteral, VariableValue)):
539 writer.add(" ")
540 return formatters
542 @staticmethod
543 def designator_handler(
544 top,
545 stack: deque,
546 writer: CodeWriterLite,
547 formatters: LiteralFormatters,
548 ) -> LiteralFormatters:
549 writer.add_line("." + top.name + " = ")
550 return formatters
552 @staticmethod
553 def formatted_literal_handler(
554 top,
555 stack: deque,
556 writer: CodeWriterLite,
557 formatters: LiteralFormatters,
558 ) -> LiteralFormatters:
559 new_formatters = formatters.replace(top.formatter)
561 stack.append(formatters)
562 stack.append(top.value)
563 stack.append(new_formatters)
564 return formatters
566 @staticmethod
567 def literal_formatters_handler(
568 top,
569 stack: deque,
570 writer: CodeWriterLite,
571 formatters: LiteralFormatters,
572 ) -> LiteralFormatters:
573 formatters = top
574 return formatters
576 TypeActionPair = namedtuple("TypeActionPair", ("types", "action"))
579_ArrStructInitGen.type_action_pairs = (
580 _ArrStructInitGen.TypeActionPair(
581 types=(CArrayLiteral), action=_ArrStructInitGen.c_array_literal_handler
582 ),
583 _ArrStructInitGen.TypeActionPair(
584 types=(CStructLiteral),
585 action=_ArrStructInitGen.c_struct_literal_handler,
586 ),
587 _ArrStructInitGen.TypeActionPair(
588 types=(_ArrStructInitGen.ClosedBrace),
589 action=_ArrStructInitGen.closed_brace_handler,
590 ),
591 _ArrStructInitGen.TypeActionPair(
592 types=(_ArrStructInitGen.OpenBrace),
593 action=_ArrStructInitGen.open_brace_handler,
594 ),
595 _ArrStructInitGen.TypeActionPair(
596 types=(CExtendedLiteral, VariableValue),
597 action=_ArrStructInitGen.literal_or_value_handler,
598 ),
599 _ArrStructInitGen.TypeActionPair(
600 types=(_ArrStructInitGen.Designator),
601 action=_ArrStructInitGen.designator_handler,
602 ),
603 _ArrStructInitGen.TypeActionPair(
604 types=(FormattedLiteral),
605 action=_ArrStructInitGen.formatted_literal_handler,
606 ),
607 _ArrStructInitGen.TypeActionPair(
608 types=(LiteralFormatters),
609 action=_ArrStructInitGen.literal_formatters_handler,
610 ),
611)
614class NoValueError(ValueError):
615 """Variable has no value and hence cannot be initialized."""
618class FuncPtr:
619 """Class describing function pointer args and return type.
621 This class made for declaring function pointer type specifics for use with
622 :class:`Variable`\\ s as their `type` argument.
624 Args:
625 return_type: :obj:`str` containing the return type
626 arguments: an iterable which yields one the following types:
628 * a :class:`Variable`
629 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
630 (`name`, `primitive`)
631 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
632 `primitive`)
633 """
635 __slots__ = ("return_type", "arguments")
637 # TODO unify the creation to be the same as Function
639 def __init__(
640 self,
641 return_type: str,
642 arguments: Optional[Iterable] = None,
643 ) -> None:
644 self.return_type = assure_str(return_type)
645 self.arguments: List[Variable] = []
647 if arguments is not None:
648 for arg in arguments:
649 self.arguments.append(_get_variable(arg))
651 def get_declaration(
652 self,
653 name: str,
654 qualifiers: Optional[str] = None,
655 array: Optional[str] = None,
656 ) -> str:
657 """Generate the whole declaration :obj:`str` according to parameters.
659 This method is meant to be called from
660 :meth:`Variable.generate_declaration` and
661 :meth:`Variable.generate_initialization` function and is probably
662 useless to You elsewhere.
663 """
664 jointargs = ", ".join(
665 arg.generate_declaration() for arg in self.arguments
666 )
668 retval = "{rt} (*{qual}{name}{arr})({arguments})".format(
669 rt=self.return_type,
670 qual=qualifiers if qualifiers else "",
671 name=name,
672 arguments=jointargs if self.arguments else "",
673 arr=array if array else "",
674 )
676 return retval
679class Variable:
680 """Class describing C variable contruct.
682 You can generate declarations and initializations for variables, and
683 variables can be initialized to very complex values (you can init an array
684 variable to an array of :ccode:`struct` containing arrays that contain
685 :ccode:`structs`, ...).
687 The formatting of initialization :obj:`str`\\ s can be altered by encasing
688 some level of the initialization with a :class:`FormattedLiteral`.
690 As for function values, the following (Python input → C code) translations
691 are assumed (`numpy` and `scipy` `int`\\ s and `float`\\ s are considered ints
692 and floats):
694 - :obj:`dict` → :ccode:`struct` literal
695 - :obj:`list` or `tuple` → array literal
696 - :obj:`int` → :ccode:`int` literal or :ccode:`int` literal
697 - :obj:`float` → :ccode:`float` literal or :ccode:`double` literal
698 - :obj:`bool` → :ccode:`bool` literal or :ccode:`int` literal
699 - :class:`Variable` → name of the variable
701 There is no type checking for initializations. That's what your compiler is
702 for, no sense in replicating such functionality here.
704 Args:
705 name: name of the variable
707 primitive: :obj:`str` name or :class:`FuncPtr` defining the variable's
708 type
710 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
711 variable's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
712 array: :class:`Sequence` of :obj:`int` defining the dimensions of a
713 (possibly multidimensional) array. If it's left out, it's inferred
714 from `value`.
716 comment: accompanying comment
717 value: variable's value, used for initialization. Explained in detail
718 above
720 """
722 __slots__ = (
723 "name",
724 "primitive",
725 "qualifiers",
726 "array",
727 "comment",
728 "value",
729 )
731 def __init__(
732 self,
733 name: str,
734 primitive: Union[str, FuncPtr],
735 qualifiers: Optional[Union[Iterable[str], str]] = None,
736 array: Optional[Iterable] = None,
737 comment: Optional[str] = None,
738 value: Optional[CLiteral] = None,
739 ) -> None:
740 self.name = assure_str(name)
741 if isinstance(primitive, FuncPtr):
742 self.primitive: Union[FuncPtr, str] = primitive
743 else:
744 self.primitive = assure_str(primitive)
745 self.comment = assure_str(comment) if comment is not None else None
746 self.array = array
747 self.qualifiers = qualifiers
748 self.value = value
750 @staticmethod
751 def merge_formatters(
752 value: CLiteral, formatters: Optional[LiteralFormatters] = None
753 ) -> Tuple[CLiteral, LiteralFormatters]:
754 formatters_ = formatters or LiteralFormatters()
756 while isinstance(value, FormattedLiteral):
757 formatters_ = formatters_.replace(value.formatter)
758 value = value.value
760 return (value, formatters_)
762 def __array_dimensions(self) -> str:
763 value = self.value
764 if value is not None:
765 value, _ = self.merge_formatters(value)
767 if isinstance(self.array, CArrayLiteral):
768 array = "".join(f"[{dim}]" for dim in self.array)
769 elif self.array is not None:
770 array = f"[{self.array}]"
771 elif self.array is None and isinstance(value, str):
772 array = "[]"
773 elif self.array is None and isinstance(value, Iterable):
774 try:
775 val_shape = _shape(value)
776 array = "".join(f"[{dim}]" for dim in val_shape)
777 except ShapelessError:
778 array = ""
779 else:
780 array = ""
782 return array
784 def init_target_code(
785 self, formatters: Optional[LiteralFormatters] = None
786 ) -> str:
787 """Return a :obj:`str` used for initialization of other
788 :obj:`Variable`\\ s"""
789 return self.name
791 def generate_declaration(self, extern: bool = False) -> str:
792 """Return a declaration :obj:`str`.
794 Doesn't end with a semicolon (;).
796 Args:
797 extern: whether or not the value is to be declared as
798 :ccode:`extern`
800 Examples:
801 >>> import numpy as np
802 >>> from csnake import Variable
803 >>> var = Variable(
804 ... "test",
805 ... primitive="int",
806 ... value=np.arange(24).reshape((2, 3, 4))
807 ... )
808 >>> print(var.generate_declaration())
809 int test[2][3][4]
810 """
812 if self.qualifiers is not None:
813 if isinstance(self.qualifiers, str):
814 qual = self.qualifiers + " "
815 else:
816 qual = " ".join(self.qualifiers) + " "
817 else:
818 qual = ""
820 array = self.__array_dimensions()
822 if isinstance(self.primitive, FuncPtr):
823 decl = self.primitive.get_declaration(
824 name=self.name, qualifiers=qual, array=array
825 )
827 return "{ext}{decl}".format(
828 ext="extern " if extern else "", decl=decl
829 )
831 return "{ext}{qual}{prim} {name}{array}".format(
832 ext="extern " if extern else "",
833 qual=qual,
834 prim=self.primitive,
835 name=self.name,
836 array=array,
837 )
839 @property
840 def declaration(self) -> str:
841 """Declaration string.
843 Ends with a semicolon (;).
845 See Also:
846 :meth:`generate_declaration` for the underlying method.
847 """
848 return (
849 self.generate_declaration(extern=False)
850 + ";"
851 + (f" /* {self.comment} */" if self.comment is not None else "")
852 )
854 def __generate_array_struct_initialization(
855 self,
856 array: Union[CArrayLiteral, CStructLiteral],
857 indent: Union[int, str] = 4,
858 writer: Optional[CodeWriterLite] = None,
859 formatters: Optional[LiteralFormatters] = None,
860 ) -> None:
861 """Print (multi)dimensional arrays."""
863 if self.value is None:
864 raise NoValueError
866 stack: deque = deque()
867 stack.append(array)
868 if not formatters:
869 formatters = LiteralFormatters()
870 if writer is None:
871 writer = CodeWriterLite(indent=indent)
873 while stack:
874 top = stack.pop()
875 for types, action in _ArrStructInitGen.type_action_pairs:
876 if isinstance(top, types):
877 formatters = action(
878 top=top,
879 stack=stack,
880 writer=writer,
881 formatters=formatters,
882 )
883 break
884 else:
885 raise TypeError("Unknown type on stack")
887 def generate_initialization(
888 self, indent: Union[int, str] = 4
889 ) -> CodeWriterLite:
890 """Return a :class:`CodeWriterLite` instance containing the
891 initialization code for this :class:`Variable`.
893 Ends with a semicolon (;).
895 Args:
896 indent: indent :obj:`str` or :obj:`int` denoting the number of
897 spaces for indentation
900 Example:
901 >>> import numpy as np
902 >>> from csnake import Variable
903 >>> var = Variable(
904 ... "test",
905 ... primitive="int",
906 ... value=np.arange(24).reshape((2, 3, 4))
907 ... )
908 >>> print(var.generate_initialization())
909 int test[2][3][4] = {
910 {
911 {0, 1, 2, 3},
912 {4, 5, 6, 7},
913 {8, 9, 10, 11}
914 },
915 {
916 {12, 13, 14, 15},
917 {16, 17, 18, 19},
918 {20, 21, 22, 23}
919 }
920 };
922 """
924 # main part: generating initializer
925 if self.value is None:
926 raise NoValueError
928 writer = CodeWriterLite(indent=indent)
930 if not isinstance(self.qualifiers, str) and isinstance(
931 self.qualifiers, Iterable
932 ):
933 qual = " ".join(self.qualifiers) + " "
934 elif self.qualifiers is not None:
935 qual = assure_str(self.qualifiers) + " "
936 else:
937 qual = ""
939 array = self.__array_dimensions()
941 if isinstance(self.primitive, FuncPtr):
942 decl = self.primitive.get_declaration(
943 name=self.name, qualifiers=qual, array=array
944 )
945 writer.add_line(decl)
946 else:
947 writer.add_line(f"{qual}{self.primitive} {self.name}{array}")
949 writer.add(" = ")
951 value, formatters = self.merge_formatters(self.value)
953 if isinstance(value, (CArrayLiteral, CStructLiteral)):
954 self.__generate_array_struct_initialization(
955 value, indent, writer, formatters
956 )
957 else:
958 formatters = LiteralFormatters()
959 assignment = generate_c_value_initializer(value, formatters)
960 writer.add(assignment)
962 writer.add(";")
963 return writer
965 @property
966 def initialization(self) -> str:
967 """Initialization :obj:`str`.
969 Ends with a semicolon (;).
971 See Also:
972 :meth:`generate_initialization` for the underlying method.
973 """
974 return str(self.generate_initialization())
976 def __str__(self):
977 """Initialization (if value in not None) or declaration :obj:`str`.
979 Falls back to declaration if :attr:`value` is None.
981 Ends with a semicolon (;).
983 See Also:
984 :meth:`generate_initialization` for the initialization method.
985 :meth:`generate_declaration` for the declaration method.
986 """
987 try:
988 return self.initialization
989 except NoValueError:
990 return self.declaration
993# no VariableValue is also a VariableValue!
994VariableValue.register(Variable)
997class BitField:
998 """Class describing C bit fields.
1000 Bit fields in C are a way of packing variables into bits.
1002 the C syntax is:
1003 :ccode:`type [name] : width ;`
1005 where
1006 * `type` is usually an integer type.
1007 * `name` is optional so we could have unnamed bits for padding..
1008 * `width` is the number of bits.
1010 Note:
1011 This class made for declaring bit field specifics for use with
1012 :meth:`Struct.add_bit_field`.
1014 Args:
1015 name: (Optional) name of the bit field
1016 width: width of the bit field
1017 primitive: primitive type of the bit field
1018 comment: accompanying comment
1019 """
1021 __slots__ = (
1022 "primitive",
1023 "width",
1024 "name",
1025 "comment",
1026 )
1028 def __init__(
1029 self,
1030 primitive: str,
1031 width: int,
1032 name: Optional[str] = None,
1033 comment: Optional[str] = None,
1034 ) -> None:
1035 self.primitive = assure_str(primitive)
1036 self.width = width
1037 self.name = name
1038 self.comment = comment
1040 @property
1041 def declaration(self) -> str:
1042 """Declaration string.
1044 Ends with a semicolon (;).
1045 Includes the comment if present.
1046 """
1048 retval = "{type} {name}: {width};".format(
1049 type=self.primitive,
1050 name=self.name if self.name is not None else "",
1051 width=self.width,
1052 )
1054 if self.comment is not None:
1055 return f"{retval} /* {self.comment} */"
1057 return retval
1060def _get_variable(variable: Union[Variable, Collection, Mapping]) -> Variable:
1061 """Get a Variable out of one of the following:
1063 * a Variable (idempotent)
1065 * a Collection (tuple/list-like) of 2 strings (name, primitive)
1067 * a Mapping (dict-like) with keys (name, primitive)
1068 """
1069 if isinstance(variable, Variable):
1070 return variable
1071 if isinstance(variable, Mapping):
1072 var = Variable(**variable)
1073 return var
1074 if isinstance(variable, Collection):
1075 if len(variable) != 2: # noqa: PLR2004
1076 raise TypeError(
1077 "variable must be a Collection with len(variable) == 2"
1078 )
1079 var = Variable(*variable)
1080 return var
1081 raise TypeError("variable must be one of (Variable, Collection, Mapping)")
1084class Struct:
1085 """Class describing C :ccode:`struct` construct.
1087 Args:
1088 name: name of struct
1089 typedef: whether or not the struct is :ccode:`typedef`'d
1091 Attributes:
1092 name: name of struct
1093 typedef: whether or not the struct is :ccode:`typedef`'d
1094 variables: :obj:`list` of :ccode:`struct`'s variables
1095 """
1097 __slots__ = ("name", "variables", "typedef")
1099 def __init__(self, name: str, typedef: bool = False) -> None:
1100 self.name = assure_str(name)
1101 self.variables: List[Union[Variable, BitField]] = []
1102 self.typedef = bool(typedef)
1104 def add_variable(self, variable: Union[Variable, Collection, Mapping]):
1105 """Add a variable to `struct`.
1107 Variables inside of a :class:`Struct` are ordered (added sequentially).
1108 The order is synced with the :meth:`add_bit_field` function.
1110 Args:
1111 variable: variable to add. It can be defined in multiple ways.
1113 `variable` can be:
1115 * a :class:`Variable`
1116 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2
1117 :obj:`str`\\ s (`name`, `primitive`)
1118 * a :class:`Mapping` (:obj:`dict`-like) with keys ['name',
1119 'primitive']
1120 """
1122 proc_var = _get_variable(variable)
1123 self.variables.append(proc_var)
1125 def add_bit_field(self, bit_field: BitField):
1126 """Add a bit field to `struct`.
1128 Bit fields inside of a :class:`Struct` are ordered (added sequentially).
1129 The order is synced with the :meth:`add_variable` function.
1131 see :class:`BitField` for more information.
1133 Args:
1134 bit_field: bit field to add.
1135 """
1136 self.variables.append(bit_field)
1138 def generate_declaration(
1139 self, indent: Union[int, str] = 4
1140 ) -> CodeWriterLite:
1141 """Generate a :class:`CodeWriterLite` instance containing the
1142 initialization code for this :class:`Struct`.
1144 Args:
1145 indent: indent :obj:`str` or :obj:`int` denoting the number of
1146 spaces for indentation
1148 Example:
1149 >>> from csnake import BitField, Variable, Struct
1150 >>> strct = Struct("strname", typedef=False)
1151 >>> var1 = Variable("var1", "int")
1152 >>> var2 = Variable("var2", "int", value=range(10))
1153 >>> strct.add_variable(var1)
1154 >>> strct.add_variable(var2)
1155 >>> strct.add_variable(("var3", "int"))
1156 >>> strct.add_variable({"name": "var4", "primitive": "int"})
1157 >>> var5 = BitField("int", 2, name="var5")
1158 >>> var6 = BitField("int", 3, comment="unused bits")
1159 >>> var7 = BitField("int", 1, name="var7")
1160 >>> strct.add_bit_field(var5)
1161 >>> strct.add_bit_field(var6)
1162 >>> strct.add_bit_field(var7)
1163 >>> print(strct.generate_declaration())
1164 struct strname
1165 {
1166 int var1;
1167 int var2[10];
1168 int var3;
1169 int var4;
1170 int var5: 2;
1171 int : 3; /* unused bits */
1172 int var7: 1;
1173 };
1174 """
1175 writer = CodeWriterLite(indent=indent)
1177 if self.typedef:
1178 writer.add_line("typedef struct")
1179 else:
1180 writer.add_line(f"struct {self.name}")
1182 writer.open_brace()
1183 for var in self.variables:
1184 writer.add_line(var.declaration)
1185 writer.close_brace()
1187 if self.typedef:
1188 writer.add(" " + self.name + ";")
1189 else:
1190 writer.add(";")
1192 return writer
1194 @property
1195 def declaration(self):
1196 """:class:`CodeWriterLite` instance containing the
1197 declaration code for this :class:`Struct`.
1199 See Also:
1200 :meth:`generate_declaration` for the underlying method.
1201 """
1203 return self.generate_declaration()
1205 def __str__(self):
1206 """Generate a :obj:`str` instance containing the
1207 declaration code for this :class:`Struct`."""
1208 return str(self.generate_declaration())
1211class AddressOf(VariableValue):
1212 """Address of (&) VariableValue for variable initialization.
1214 Subclass of :class:`VariableValue` that returns an initialization string
1215 containing the & (address of) used on a value.
1217 Args:
1218 variable: variable to return the address of.
1220 Attributes:
1221 variable: variable to return the address of.
1223 Examples:
1224 >>> from csnake import Variable, AddressOf
1225 >>> var1 = Variable("var1", "int")
1226 >>> addrof_var1 = AddressOf(var1)
1227 >>> var2 = Variable("var2", "int", value=addrof_var1)
1228 >>> print(var2)
1229 int var2 = &var1;
1230 """
1232 def __init__(self, variable: Union[VariableValue]) -> None:
1233 if not isinstance(variable, (VariableValue)):
1234 raise TypeError("variable must be of type VariableValue.")
1235 self.variable = variable
1237 def init_target_code(
1238 self, formatters: Optional[LiteralFormatters] = None
1239 ) -> str:
1240 """Return a :obj:`str` used for initialization of other
1241 :obj:`Variable`\\ s"""
1242 return f"&{generate_c_value_initializer(self.variable, formatters)}"
1244 def __str__(self):
1245 """Initialization string."""
1246 return self.init_target_code()
1249class Dereference(VariableValue):
1250 """Dereference (*) modifier for variable initialization.
1252 Subclass of :class:`VariableValue` that returns an initialization string
1253 containing the * (dereference) used on a value.
1255 Args:
1256 value: value to dereference.
1258 Attributes:
1259 value: value to dereference.
1261 Examples:
1262 >>> from csnake import Variable, Dereference
1263 >>> var1 = Variable("var1", "int")
1264 >>> derefd_var1 = Dereference(var1)
1265 >>> var2 = Variable("var2", "int", value=derefd_var1)
1266 >>> print(var2)
1267 int var2 = *var1;
1268 >>> derefd_number = Dereference(16)
1269 >>> var3 = Variable("var3", "int", value=derefd_number)
1270 >>> print(var3)
1271 int var3 = *16;
1272 """
1274 __slots__ = ("value",)
1276 def __init__(self, value: Union[CExtendedLiteral, VariableValue]) -> None:
1277 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1278 raise TypeError("value must be of type VariableValue or CLiteral.")
1279 self.value = value
1281 def init_target_code(
1282 self, formatters: Optional[LiteralFormatters] = None
1283 ) -> str:
1284 """Return a :obj:`str` used for initialization of other
1285 :obj:`value`\\ s"""
1286 return f"*{generate_c_value_initializer(self.value, formatters)}"
1288 def __str__(self):
1289 """Initialization string."""
1290 return self.init_target_code()
1293class Typecast(VariableValue):
1294 """Typecast modifier for variable initialization.
1296 Subclass of :class:`VariableValue` that returns an initialization string
1297 containing the typecast to `type` used on a value.
1299 Args:
1300 value: value to be typecast
1301 cast: type to cast to
1303 Attributes:
1304 value: value to be typecast
1305 cast: type to cast to
1307 Examples:
1308 >>> from csnake import Variable, Typecast
1309 >>> var1 = Variable("var1", "int")
1310 >>> cast_var1 = Typecast(var1, 'long')
1311 >>> var2 = Variable("var2", "int", value=cast_var1)
1312 >>> print(var2)
1313 int var2 = (long) var1;
1314 >>> cast_number = Typecast(16, 'long')
1315 >>> var3 = Variable("var3", "int", value=cast_number)
1316 >>> print(var3)
1317 int var3 = (long) 16;
1318 """
1320 __slots__ = ("subscript", "cast")
1322 def __init__(
1323 self, value: Union[CExtendedLiteral, VariableValue], cast: str
1324 ) -> None:
1325 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1326 raise TypeError(
1327 "variable must be of type VariableValue or CLiteral."
1328 )
1329 self.value = value
1330 self.cast = assure_str(cast)
1332 def init_target_code(
1333 self, formatters: Optional[LiteralFormatters] = None
1334 ) -> str:
1335 """Return a :obj:`str` used for initialization of other
1336 :obj:`value`\\ s"""
1337 initializer = generate_c_value_initializer(self.value, formatters)
1338 return f"({self.cast}) {initializer}"
1340 def __str__(self) -> str:
1341 """Initialization string."""
1342 return self.init_target_code()
1345class Subscript(VariableValue):
1346 """Subscript ([]) modifier for variable initialization.
1348 Subclass of :class:`VariableValue` that returns an initialization string
1349 with a subscripted value.
1351 Args:
1352 variable: variable to be typecast
1353 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or
1354 `tuple` of them representing the subscript[s].
1356 Attributes:
1357 variable: variable to be typecast
1358 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or
1359 `tuple` of them representing the subscript[s].
1361 Examples:
1362 >>> from csnake import Variable, Subscript
1363 >>> var1 = Variable("var1", "int")
1364 >>> subscr1_var1 = Subscript(var1, 3)
1365 >>> var2 = Variable("var2", "int", value=subscr1_var1)
1366 >>> print(var2)
1367 int var2 = var1[3];
1368 >>> subscr2_var1 = Subscript(var1, (3, 2))
1369 >>> var3 = Variable("var3", "int", value=subscr2_var1)
1370 >>> print(var3)
1371 int var3 = var1[3][2];
1372 >>> subscr2_var1 = Subscript(var1, (var3, 2))
1373 >>> var4 = Variable("var4", "int", value=subscr2_var1)
1374 >>> print(var4)
1375 int var4 = var1[var3][2];
1376 """
1378 __slots__ = ("subscript", "variable")
1380 def __init__(
1381 self, variable: Union[CLiteral, VariableValue], subscript: Iterable
1382 ) -> None:
1383 if not isinstance(variable, VariableValue):
1384 raise TypeError("variable must be VariableValue.")
1385 self.variable = variable
1387 if not isinstance(
1388 subscript, (CIntLiteral, Iterable, VariableValue, CArrayLiteral)
1389 ):
1390 raise TypeError(
1391 "Subscript must be an CIntLiteral, VariableValue "
1392 "(or Variable), list or tuple."
1393 )
1395 if not subscript:
1396 raise TypeError("Subscript must be non-empty.")
1398 try:
1399 self.subscript = list(subscript)
1400 except TypeError:
1401 self.subscript = [subscript]
1403 def init_target_code(
1404 self, formatters: Optional[LiteralFormatters] = None
1405 ) -> str:
1406 """Return a :obj:`str` used for initialization of other
1407 :obj:`Variable`\\ s"""
1409 return generate_c_value_initializer(
1410 self.variable, formatters
1411 ) + "".join(
1412 f"[{generate_c_value_initializer(dim, formatters)}]"
1413 for dim in self.subscript
1414 )
1416 def __str__(self):
1417 """Initialization string."""
1418 return self.init_target_code()
1421class Dot(VariableValue):
1422 """Dot (.) VariableValue for variable initialization.
1424 Subclass of :class:`VariableValue` that returns an initialization string
1425 for accessing a specific member of a struct.
1427 Args:
1428 variable: variable whose member we're accessing
1429 member: name of member we're accessing
1431 Attributes:
1432 variable: variable whose member we're accessing
1433 member: name of member we're accessing
1435 Examples:
1436 >>> from csnake import Variable, Dot
1437 >>> var1 = Variable("var1", "struct somestr")
1438 >>> dotvar = Dot(var1, 'some_member')
1439 >>> var2 = Variable("var2", "int", value=dotvar)
1440 >>> print(var2)
1441 int var2 = var1.some_member;
1442 """
1444 __slots__ = ("variable", "member")
1446 def __init__(
1447 self, variable: Union[CLiteral, VariableValue], member: str
1448 ) -> None:
1449 if not isinstance(variable, VariableValue):
1450 raise TypeError("variable must be VariableValue.")
1451 self.variable = variable
1453 if not isinstance(member, (VariableValue, str)):
1454 raise TypeError("member must be either a VariableValue or a str.")
1455 self.member = member
1457 def init_target_code(
1458 self, formatters: Optional[LiteralFormatters] = None
1459 ) -> str:
1460 """Return a :obj:`str` used for initialization of other
1461 :obj:`Variable`\\ s"""
1462 if isinstance(self.member, str):
1463 return (
1464 self.variable.init_target_code(formatters) + "." + self.member
1465 )
1466 if isinstance(self.member, VariableValue):
1467 return (
1468 self.variable.init_target_code(formatters)
1469 + "."
1470 + self.member.init_target_code(formatters)
1471 )
1472 raise TypeError("member must be either a VariableValue or a str.")
1474 def __str__(self):
1475 """Initialization string."""
1476 return self.init_target_code()
1479class Arrow(VariableValue):
1480 """Arrow (->) VariableValue for variable initialization.
1482 Subclass of :class:`VariableValue` that returns an initialization string
1483 for accessing a specific member of a struct indirectly (through a pointer).
1485 Args:
1486 variable: variable whose member we're accessing
1487 member: name of member we're accessing
1489 Attributes:
1490 variable: variable whose member we're accessing
1491 member: name of member we're accessing
1493 Examples:
1494 >>> from csnake import Variable, Arrow
1495 >>> var1 = Variable("var1", "struct somestr")
1496 >>> arrvar = Arrow(var1, 'some_member')
1497 >>> var2 = Variable("var2", "int", value=arrvar)
1498 >>> print(var2)
1499 int var2 = var1->some_member;
1500 """
1502 __slots__ = "variable", "item"
1504 def __init__(
1505 self, variable: Union[CLiteral, VariableValue], item: str
1506 ) -> None:
1507 if not isinstance(variable, VariableValue):
1508 raise TypeError("variable must be VariableValue.")
1509 self.variable = variable
1511 if not isinstance(item, (VariableValue, str)):
1512 raise TypeError("item must be either a VariableValue or a str.")
1513 self.item = item
1515 def init_target_code(
1516 self, formatters: Optional[LiteralFormatters] = None
1517 ) -> str:
1518 """Return a :obj:`str` used for initialization of other
1519 :obj:`Variable`\\ s"""
1520 if isinstance(self.item, str):
1521 return (
1522 self.variable.init_target_code(formatters) + "->" + self.item
1523 )
1524 if isinstance(self.item, VariableValue):
1525 return (
1526 self.variable.init_target_code(formatters)
1527 + "->"
1528 + self.item.init_target_code(formatters)
1529 )
1530 raise TypeError("item must be either a VariableValue or a str.")
1532 def __str__(self):
1533 """Initialization string."""
1534 return self.init_target_code()
1537class GenericModifier(VariableValue):
1538 """VariableValue generated by applying a function to a value.
1540 A value's initializer string is passed through the supplied function, which
1541 should return a :obj:`str`.
1543 Args:
1544 value: value whose initializer we're passing through a function
1545 format_function: function used for modifying a value's initializer
1547 Attributes:
1548 value: value whose initializer we're passing through a function
1549 format_function: function used for modifying a value's initializer
1551 Examples:
1552 >>> from csnake import Variable, GenericModifier
1553 >>> var1 = Variable("var1", "int")
1554 >>> genmod_var1 = GenericModifier(var1, lambda l: f'TRANSLATE({l})')
1555 >>> var2 = Variable("var2", "int", value=genmod_var1)
1556 >>> print(var2)
1557 int var2 = TRANSLATE(var1);
1558 >>> genmod_var2 = GenericModifier('test', lambda l: f'do_whatever({l})')
1559 >>> var3 = Variable("var3", "int", value=genmod_var2)
1560 >>> print(var3)
1561 int var3 = do_whatever("test");
1562 """
1564 __slots__ = ("value", "format_function")
1566 def __init__(
1567 self, value: Union[CExtendedLiteral, VariableValue], function: Callable
1568 ) -> None:
1569 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1570 raise TypeError(
1571 "value must be of type VariableValue or CExtendedLiteral."
1572 )
1573 self.value = value
1574 self.format_function = function
1576 def init_target_code(
1577 self, formatters: Optional[LiteralFormatters] = None
1578 ) -> str:
1579 """Return a :obj:`str` used for initialization of other
1580 :obj:`Variable`\\ s"""
1581 return self.format_function(generate_c_value_initializer(self.value))
1583 def __str__(self):
1584 """Initialization string."""
1585 return self.init_target_code()
1588class OffsetOf(VariableValue):
1589 """offsetof VariableValue modifier for initializing to offsets of struct
1590 members.
1592 Subclass of :class:`VariableValue` that returns an initialization string
1593 containing the offset of a specific member of a struct.
1596 Args:
1597 struct: struct whose member's offset we're using
1598 member: struct member whose offset we're using
1600 Attributes:
1601 struct: struct whose member's offset we're using
1602 member: struct member whose offset we're using
1604 Examples:
1605 >>> from csnake import Variable, OffsetOf, Struct
1606 >>> offs_val1 = OffsetOf('struct some', 'some_member')
1607 >>> var2 = Variable("var2", "int", value=offs_val1 )
1608 >>> print(var2)
1609 int var2 = offsetof(struct some, some_member);
1610 >>> test_struct = Struct('other')
1611 >>> offs_val2 = OffsetOf(test_struct, 'other_member')
1612 >>> var3 = Variable("var3", "int", value=offs_val2 )
1613 >>> print(var3)
1614 int var3 = offsetof(struct other, other_member);
1615 """
1617 __slots__ = ("struct", "member")
1619 def __init__(
1620 self, struct: Union[Struct, str], member: Union[VariableValue, str]
1621 ) -> None:
1622 if not isinstance(struct, (str, Struct)):
1623 raise TypeError("First argument must be either a Struct or a str.")
1624 self.struct = struct
1626 if not isinstance(member, (VariableValue, str)):
1627 raise TypeError(
1628 "Second argument must be either a VariableValue or a str"
1629 )
1630 self.member = member
1632 def init_target_code(
1633 self, formatters: Optional[LiteralFormatters] = None
1634 ) -> str:
1635 """Return a :obj:`str` used for initialization of other
1636 :obj:`Variable`\\ s"""
1637 if isinstance(self.struct, str):
1638 struct_name = self.struct
1639 elif isinstance(self.struct, Struct):
1640 if self.struct.typedef:
1641 struct_name = self.struct.name
1642 else:
1643 struct_name = "struct " + self.struct.name
1645 if isinstance(self.member, str):
1646 member_name = self.member
1647 elif isinstance(self.member, VariableValue):
1648 member_name = self.member.init_target_code(formatters)
1650 return f"offsetof({struct_name}, {member_name})"
1652 def __str__(self):
1653 """Initialization string."""
1654 return self.init_target_code()
1657class TextModifier(VariableValue):
1658 """Generic textual VariableValue for initializing to an arbitrary
1659 :obj:`str`.
1661 The :obj:`str` supplied as the argument is output verbatim as the
1662 Initialization string.
1664 Args:
1665 text: initialization string
1667 Attributes:
1668 text: initialization string
1670 Examples:
1671 >>> from csnake import Variable, TextModifier
1672 >>> textmod1 = TextModifier('whatever + you + want')
1673 >>> var2 = Variable("var2", "int", value=textmod1)
1674 >>> print(var2)
1675 int var2 = whatever + you + want;
1676 >>> textmod2 = TextModifier(f'you_can_do_this_too + {var2.name}')
1677 >>> var3 = Variable("var3", "int", value=textmod2)
1678 >>> print(var3)
1679 int var3 = you_can_do_this_too + var2;
1680 """
1682 __slots__ = ("text",)
1684 def __init__(self, text: str) -> None:
1685 self.text = assure_str(text)
1687 def init_target_code(
1688 self, formatters: Optional[LiteralFormatters] = None
1689 ) -> str:
1690 """Return a :obj:`str` used for initialization of other
1691 :obj:`Variable`\\ s"""
1692 return self.text
1694 def __str__(self):
1695 """Initialization string."""
1696 return self.init_target_code()
1699class Function:
1700 """Class describing C function.
1702 You can generate a function prototype (declaration), a definition or a
1703 call.
1704 A function's body is a :class:`CodeWriterLite`, so You can add lines of
1705 code to it (or a whole new :class:`CodeWriter` instance).
1707 Args:
1708 name: name of the function
1709 return_type: :obj:`str` containing function's return type
1711 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
1712 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
1714 arguments: an iterable which yields one the following types:
1716 * a :class:`Variable`
1717 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1718 (`name`, `primitive`)
1719 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1720 `primitive`)
1723 Attributes:
1724 name: name of the function
1725 return_type: :obj:`str` containing function's return type
1727 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
1728 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
1730 arguments: an iterable which yields one the following types:
1732 * a :class:`Variable`
1733 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1734 (`name`, `primitive`)
1735 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1736 `primitive`)
1738 codewriter: internal instance of :class:`CodeWriterLite` that contains
1739 the funtion's body code.
1740 """
1742 __slots__ = (
1743 "name",
1744 "return_type",
1745 "arguments",
1746 "qualifiers",
1747 "codewriter",
1748 )
1750 def __init__(
1751 self,
1752 name: str,
1753 return_type: str = "void",
1754 qualifiers: Optional[Union[str, Iterable[str]]] = None,
1755 arguments: Optional[Iterable] = None,
1756 ) -> None:
1757 self.name = assure_str(name)
1758 self.return_type = assure_str(return_type)
1759 self.arguments: List[Variable] = []
1760 self.codewriter = CodeWriterLite()
1762 if isinstance(qualifiers, str):
1763 self.qualifiers = qualifiers.split()
1764 elif qualifiers is not None:
1765 self.qualifiers = [
1766 assure_str(qualifier) for qualifier in qualifiers
1767 ]
1768 else:
1769 self.qualifiers = []
1771 if arguments is not None:
1772 for arg in arguments:
1773 proc_arg = _get_variable(arg)
1774 self.add_argument(proc_arg)
1776 def add_argument(self, arg: Variable) -> None:
1777 """Add an argument to function.
1779 Arguments are added sequentially.
1781 arg: an one the following types:
1783 * a :class:`Variable`
1784 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1785 (`name`, `primitive`)
1786 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1787 `primitive`)
1788 """
1790 proc_arg = _get_variable(arg)
1792 self.arguments.append(proc_arg)
1794 def add_arguments(self, args: Iterable[Variable]) -> None:
1795 """Add multiple arguments to function.
1797 Arguments are added sequentially.
1799 args: an iterable which yields one the following types:
1801 * a :class:`Variable`
1802 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1803 (`name`, `primitive`)
1804 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1805 `primitive`)
1806 """
1808 for arg in args:
1809 self.add_argument(arg)
1811 def add_code(self, code: Union[str, Iterable[str]]) -> None:
1812 """Add a :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to function's body
1814 Since a :class:`CodeWriter` is an iterable of :obj:`str`\\ s, you can
1815 simply add its contents to the function by passing it to this method.
1818 Args:
1819 code: :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to be added
1820 """
1822 self.codewriter.add_lines(code)
1824 def generate_call(self, *arg) -> str:
1825 """Return function calling code for specific arguments.
1827 Args:
1828 \\*arg: argument values for call, in order of appearance.
1830 Doesn't end with a semicolon (;).
1832 Examples:
1833 >>> from csnake import Variable, Function
1834 >>> arg1 = Variable("arg1", "int")
1835 >>> arg2 = Variable("arg2", "int", value=range(10))
1836 >>> arg3 = ("arg3", "int")
1837 >>> arg4 = {"name": "arg4", "primitive": "int"}
1838 >>> fun = Function(
1839 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1840 ... )
1841 >>> fun.add_code(("code;", "more_code;"))
1842 >>> print(fun.generate_call(1, 2, 3, 4))
1843 testfunct(1, 2, 3, 4)
1844 """
1846 # if the arguments are all given in a single iterable
1847 if len(arg) == 1 and isinstance(next(iter(arg)), Iterable):
1848 return self.generate_call(*next(iter(arg)))
1850 if not len(arg) == len(self.arguments):
1851 raise ValueError("number of arguments must match")
1853 arg_str = ", ".join(str(argument) for argument in arg)
1854 return f"{self.name}({arg_str})"
1856 def generate_prototype(self, extern: bool = False) -> str:
1857 """Generate function prototype code.
1859 Args:
1860 extern: whether or not the function is to be declared as
1861 :ccode:`extern`
1863 Doesn't end with a semicolon (;).
1865 Examples:
1866 >>> from csnake import Variable, Function
1867 >>> arg1 = Variable("arg1", "int")
1868 >>> arg2 = Variable("arg2", "int", value=range(10))
1869 >>> arg3 = ("arg3", "int")
1870 >>> arg4 = {"name": "arg4", "primitive": "int"}
1871 >>> fun = Function(
1872 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1873 ... )
1874 >>> fun.add_code(("code;", "more_code;"))
1875 >>> print(fun.generate_prototype())
1876 void testfunct(int arg1, int arg2[10], int arg3, int arg4)
1877 """
1878 return "{extern}{qual}{ret} {nm}({args})".format(
1879 extern="extern " if extern else "",
1880 qual=" ".join(self.qualifiers) + " " if self.qualifiers else "",
1881 ret=self.return_type,
1882 nm=self.name,
1883 args=", ".join(
1884 var.generate_declaration() for var in self.arguments
1885 )
1886 if self.arguments
1887 else "void",
1888 )
1890 @property
1891 def prototype(self) -> str:
1892 """Function prototype (declaration) :obj:`str`.
1894 Ends with a semicolon (;).
1896 See Also:
1897 :meth:`generate_prototype` for the underlying method.
1898 """
1899 return self.generate_prototype() + ";"
1901 def generate_definition(
1902 self, indent: Union[int, str] = " "
1903 ) -> CodeWriterLite:
1904 """Return a :class:`CodeWriterLite` instance containing the
1905 definition code for this :class:`Function`.
1907 Args:
1908 indent: indent :obj:`str` or :obj:`int` denoting the number of
1909 spaces for indentation
1911 Examples:
1912 >>> from csnake import Variable, Function
1913 >>> arg1 = Variable("arg1", "int")
1914 >>> arg2 = Variable("arg2", "int", value=range(10))
1915 >>> arg3 = ("arg3", "int")
1916 >>> arg4 = {"name": "arg4", "primitive": "int"}
1917 >>> fun = Function(
1918 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1919 ... )
1920 >>> fun.add_code(("code;", "more_code;"))
1921 >>> print(fun.generate_definition())
1922 void testfunct(int arg1, int arg2[10], int arg3, int arg4)
1923 {
1924 code;
1925 more_code;
1926 }
1928 """
1929 writer = CodeWriterLite(indent=indent)
1930 writer.add_line(self.generate_prototype())
1931 writer.open_brace()
1933 writer.add_lines(self.codewriter) # type: ignore
1935 writer.close_brace()
1937 return writer
1939 @property
1940 def definition(self) -> str:
1941 """Function definition :obj:`str`.
1943 See Also:
1944 :meth:`generate_definition` for the underlying method.
1945 """
1946 return self.generate_definition().code
1948 def init_target_code(
1949 self, formatters: Optional[LiteralFormatters] = None
1950 ) -> str:
1951 """Return code used for variable initialization, formatted with the
1952 supplied formatters.
1954 Args:
1955 formatters: collection of formatters used for formatting the
1956 initialization :obj:`str`
1957 """
1958 return self.name
1961# TODO: allow for functions to be used to initialize function pointers
1963VariableValue.register(Function)
1966class Enum:
1967 """Class describing C :ccode:`enum` construct.
1969 Args:
1970 name: name of the enum
1971 prefix: prefix to add to every enumeration's name
1972 typedef: whether or not the enum is :ccode:`typedef`'d
1974 Attributes:
1975 name: name of the enum
1976 typedef: whether or not the enum is :ccode:`typedef`'d
1977 prefix: prefix to add to every enumeration's name
1978 values: `list` of :class:`Enum.EnumValue`
1979 """
1981 __slots__ = ("typedef", "values", "name", "prefix")
1983 class EnumValue:
1984 """Singular value of an C-style enumeration."""
1986 __slots__ = ("name", "value", "comment")
1988 def __init__(
1989 self,
1990 name: str,
1991 value: Optional[Union[CLiteral, VariableValue]] = None,
1992 comment: Optional[str] = None,
1993 ) -> None:
1994 self.name = assure_str(name)
1995 if value is not None and not isinstance(
1996 value, (CLiteral, VariableValue)
1997 ):
1998 raise ValueError(
1999 f"value ({value}) must be one of (CLiteral, VariableValue)"
2000 )
2001 self.value = value
2002 self.comment = assure_str(comment) if comment is not None else None
2004 def __init__(
2005 self, name: str, prefix: Optional[str] = None, typedef: bool = False
2006 ) -> None:
2007 self.typedef = bool(typedef)
2008 # enum values
2009 self.values: List[Enum.EnumValue] = []
2010 self.name = assure_str(name)
2012 if prefix is not None:
2013 self.prefix = assure_str(prefix)
2014 else:
2015 self.prefix = ""
2017 def add_value(
2018 self,
2019 name: str,
2020 value: Optional[Union[CLiteral, VariableValue]] = None,
2021 comment: Optional[str] = None,
2022 ) -> None:
2023 """Add a single :ccode:`name = value` pair (or just :ccode:`name`)."""
2024 self.values.append(self.EnumValue(name, value=value, comment=comment))
2026 def add_values(
2027 self,
2028 values: Union[Mapping, Collection[Union[Mapping, Collection, str]]],
2029 ) -> None:
2030 """Add multiple :ccode:`name = value` pairs (or just :ccode:`name`\\ s).
2032 Values can be one of:
2034 * a :obj:`dict` with `name` : `value` entries
2035 * a collection of:
2036 * :obj:`str`\\ s denoting names (no value)
2037 * :obj:`dict`\\ s with keys [`name`, `value`, `comment`], and optional
2038 keys [`value`, `comment`]
2039 * collections (:obj:`list`-like) of length 1-3 denoting [`name`,
2040 `value`, `comment`] respectively
2042 """
2043 if isinstance(values, Mapping):
2044 for name, value in values.items():
2045 self.add_value(name, value)
2047 else:
2048 for value in values:
2049 if isinstance(value, str):
2050 self.add_value(value)
2051 elif isinstance(value, Mapping):
2052 self.add_value(**value)
2053 # lists and tuples
2054 else:
2055 defaults = (None, None, None)
2056 name = seq_get(value, defaults, 0)
2057 value_ = seq_get(value, defaults, 1)
2058 comment = seq_get(value, defaults, 2)
2060 name = assure_str(name)
2062 self.add_value(name, value_, comment)
2064 def generate_declaration(
2065 self, indent: Union[int, str] = 4
2066 ) -> CodeWriterLite:
2067 """Generate enum declaration code.
2069 Generate a :class:`CodeWriterLite` instance containing the
2070 initialization code for this :class:`Enum`.
2072 Args:
2073 indent: indent :obj:`str` or :obj:`int` denoting the number of
2074 spaces for indentation
2077 Examples:
2078 >>> from csnake import (
2079 ... Enum, Variable, Dereference, AddressOf
2080 ... )
2081 >>> name = "somename"
2082 >>> pfx = "pfx"
2083 >>> typedef = False
2084 >>> enum = Enum(name, prefix=pfx, typedef=typedef)
2085 >>> cval1 = Variable("varname", "int")
2086 >>> enum.add_value("val1", 1)
2087 >>> enum.add_value("val2", Dereference(1000))
2088 >>> enum.add_value("val3", cval1)
2089 >>> enum.add_value("val4", AddressOf(cval1), "some comment")
2090 >>> print(enum.generate_declaration())
2091 enum somename
2092 {
2093 pfxval1 = 1,
2094 pfxval2 = *1000,
2095 pfxval3 = varname,
2096 pfxval4 = &varname /* some comment */
2097 };
2098 """
2100 gen = CodeWriterLite(indent=indent)
2102 if self.typedef:
2103 gen.add_line("typedef enum")
2104 else:
2105 gen.add_line("enum {name}".format(name=self.name))
2106 gen.open_brace()
2108 def _generate_lines(
2109 values: Iterable, lastline: bool = False
2110 ) -> Iterator:
2111 return (
2112 (
2113 "{prefix}{name}{value}{comma}".format(
2114 prefix=self.prefix,
2115 name=assure_str(val.name),
2116 value=(" = " + generate_c_value_initializer(val.value))
2117 if val.value is not None
2118 else "",
2119 comma="," if not lastline else "",
2120 ),
2121 val.comment,
2122 )
2123 for val in values
2124 )
2126 commalines = _generate_lines(self.values[:-1], lastline=False)
2127 lastline = _generate_lines(self.values[-1:], lastline=True)
2129 for line in chain(commalines, lastline):
2130 gen.add_line(*line)
2132 gen.close_brace()
2134 if self.typedef:
2135 gen.add(" " + self.name + ";")
2136 else:
2137 gen.add(";")
2139 return gen
2141 @property
2142 def declaration(self) -> str:
2143 """The declaration :obj:`str` (with indentation of 4 spaces).
2146 See Also:
2147 :meth:`generate_declaration` for the underlying method.
2148 """
2149 return self.generate_declaration().code
2151 def __str__(self) -> str:
2152 """
2153 Return the declaration :obj:`str` (with indentation of 4 spaces).
2154 """
2155 return self.declaration