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"""
5# TODO: make a static default for LiteralFormatters for using as default, instead
6# of using another instance every time.
7# TODO: add typedefs, especially for function pointers
8import collections
9from abc import ABCMeta
10from abc import abstractmethod
11from collections import deque
12from collections import namedtuple
13from itertools import chain
14from typing import Callable
15from typing import Collection
16from typing import Iterable
17from typing import Iterator
18from typing import List
19from typing import Mapping
20from typing import Tuple
21from typing import Union
23from .codewriterlite import CodeWriterLite
24from .utils import assure_str
25from .utils import seq_get
28# internal types used by csnake as initialization values
29class CIntLiteral(metaclass=ABCMeta):
30 """ABC for all C integer literals."""
33CIntLiteral.register(int)
36class CFloatLiteral(metaclass=ABCMeta):
37 """ABC for all C floating point literals."""
40CFloatLiteral.register(float)
43class CArrayLiteral(metaclass=ABCMeta):
44 """ABC for array literals: sized Iterables except str, Mapping."""
46 @classmethod
47 def __subclasshook__(cls, subclass):
48 if cls is CArrayLiteral:
49 if (
50 issubclass(subclass, collections.abc.Sized)
51 and issubclass(subclass, collections.abc.Iterable)
52 and not issubclass(subclass, (str, collections.abc.Mapping))
53 ):
54 return True
55 return False
56 return NotImplemented
59CArrayLiteral.register(list)
60CArrayLiteral.register(tuple)
63class CStructLiteral(metaclass=ABCMeta):
64 """ABC for struct literals: any Mapping (dict-like)."""
66 @classmethod
67 def __subclasshook__(cls, subclass):
68 if cls is CStructLiteral:
69 return issubclass(subclass, collections.abc.Mapping)
70 return NotImplemented
73CStructLiteral.register(dict)
76class CBasicLiteral(metaclass=ABCMeta):
77 """ABC for literals of basic C types (int, float)."""
80CBasicLiteral.register(CIntLiteral)
81CBasicLiteral.register(CFloatLiteral)
84class CExtendedLiteral(metaclass=ABCMeta):
85 """ABC for basic C literals (int, float) + bool and char[]."""
88CExtendedLiteral.register(CBasicLiteral)
89CExtendedLiteral.register(bool)
90CExtendedLiteral.register(str)
93class CLiteral(metaclass=ABCMeta):
94 """ABC for any C literal."""
96 @classmethod
97 def __subclasshook__(cls, subclass):
98 if cls is CLiteral:
99 return issubclass(
100 subclass, (CExtendedLiteral, CStructLiteral, CArrayLiteral)
101 )
102 return NotImplemented
105CLiteral.register(CExtendedLiteral)
106CLiteral.register(CStructLiteral)
108# conditional imports
109try:
110 import numpy as np
111except ImportError:
112 pass
113else:
114 CIntLiteral.register(np.integer)
115 CFloatLiteral.register(np.floating)
117try:
118 import sympy as sp
119except ImportError:
120 pass
121else:
122 CIntLiteral.register(sp.Integer)
123 CFloatLiteral.register(sp.Float)
126class ShapelessError(TypeError):
127 """Argument doesn't have a shape."""
130def _shape(array: Iterable) -> tuple:
131 """Return dimensions (shape) of a multidimensional list."""
133 try:
134 return array.shape # type: ignore
135 except AttributeError:
136 pass
138 if isinstance(array, str):
139 raise ShapelessError("Strings don't have a shape.")
141 if isinstance(array, Mapping):
142 raise ShapelessError("Mappings (like dicts) don't have a shape.")
144 curr = array
145 shp: List[int] = []
147 while True:
148 if not isinstance(curr, CArrayLiteral):
149 return tuple(shp)
150 try:
151 shp.append(len(curr))
152 itr = iter(curr)
153 curr = next(itr)
154 except (TypeError, IndexError):
155 return tuple(shp)
158# C constructs:
161def simple_bool_formatter(value: bool) -> str:
162 """Simple formatter that turns a bool into 'true' or 'false'.
164 Args:
165 value: boolean to format
167 Returns:
168 :obj:`str`'s 'true' or 'false'
169 """
170 if value:
171 return "true"
172 else:
173 return "false"
176def simple_str_formatter(value: str) -> str:
177 """Simple formatter that surrounds a str with quotes.
179 Args:
180 value: string to format
182 Returns:
183 :obj:`str` that's the input string surrounded with double quotes
184 """
185 return f'"{value}"'
188class LiteralFormatters:
189 """Collection of formatters used for formatting literals.
191 Any values left out from constructor call is set to defaults.
193 Args:
194 int_formatter: function to call when formatting :ccode:`int` literals
195 float_formatter: function to call when formatting :ccode:`float` literals
196 bool_formatter: function to call when formatting :ccode:`bool` literals
197 string_formatter: function to call when formatting :ccode:`char[]` literals
199 Attributes:
200 int_formatter: function to call when formatting :ccode:`int` literals
201 float_formatter: function to call when formatting :ccode:`float` literals
202 bool_formatter: function to call when formatting :ccode:`bool` literals
203 string_formatter: function to call when formatting :ccode:`char[]` literals
204 """
206 __slots__ = (
207 "int_formatter",
208 "float_formatter",
209 "string_formatter",
210 "bool_formatter",
211 )
213 def __init__(
214 self,
215 int_formatter: Callable = int,
216 float_formatter: Callable = float,
217 bool_formatter: Callable = simple_bool_formatter,
218 string_formatter: Callable = simple_str_formatter,
219 ) -> None:
220 self.int_formatter = int_formatter
221 self.float_formatter = float_formatter
222 self.bool_formatter = bool_formatter
223 self.string_formatter = string_formatter
225 @classmethod
226 def empty(cls):
227 """Returns an empty :class: instance (`None` for every parameter)."""
228 return cls(**{key: None for key in cls.__slots__})
230 def __repr__(self):
231 """Simple printout of parameter values."""
232 lines = (f" {slot}: {getattr(self, slot)}" for slot in self.__slots__)
233 return "formatters {\n" + "\n".join(lines) + "\n}"
235 def replace(self, *args, **kwargs):
236 """Return a copy of collection with certain formatters replaced."""
238 if len(args) == 1 and not kwargs:
239 arg = next(iter(args))
240 if isinstance(arg, LiteralFormatters):
242 initializer = {
243 slot: getattr(arg, slot)
244 if getattr(arg, slot) is not None
245 else getattr(self, slot)
246 for slot in self.__slots__
247 }
249 return LiteralFormatters(**initializer)
251 if isinstance(arg, Mapping):
252 return self.replace(**arg)
254 if kwargs and not args:
256 diff = set(kwargs.keys()) - set(self.__slots__)
257 if diff:
258 raise TypeError(
259 f"Arguments {str(diff)} are invalid for this function"
260 )
262 initializer = {
263 slot: kwargs[slot] if slot in kwargs else getattr(self, slot)
264 for slot in self.__slots__
265 }
267 return LiteralFormatters(**initializer)
269 raise TypeError("Invalid arguments for this function")
271 @classmethod
272 def partial(cls, dictionary: Mapping):
273 """Create a partial formatter collection based on a dict."""
274 rtnval = cls.empty()
275 return rtnval.replace(dictionary)
278class FormattedLiteral:
279 """A C literal with its formatter collection altered.
281 The accompanying literal formatter collection will be used when generating
282 the initialization :obj:`str`.
284 Args:
285 \\*\\*kwargs: keyword arguments for each of the formatters in
286 ['int_formatter', 'float_formatter', 'bool_formatter',
287 'string_formatter'] we want to change for this literal. Every
288 missing formatter is inherited from the literal (or default).
290 Attributes:
291 value: value of literal
292 formatter: collection of literal formatters
293 """
295 __slots__ = ("value", "formatter")
297 def __init__(self, value: CLiteral, *args, **kwargs) -> None:
298 self.value = value
299 if args:
300 raise ValueError(f"Unexpected arguments: {args}")
302 self.formatter = LiteralFormatters.partial(kwargs)
305class VariableValue(metaclass=ABCMeta):
306 """Abstract base class for any initializer value based on a variable.
308 Sometimes we want to initialize a value to another variable, but in some
309 more complicated manner: using the address-of operator, dereference
310 operator, subscripting, typecasting... This is an ABC for those
311 modifiers.
312 """
314 @abstractmethod
315 def init_target_code(self, formatters: LiteralFormatters = None):
316 """Return code used for variable initialization, formatted with the
317 supplied formatters.
319 Args:
320 formatters: collection of formatters used for formatting the
321 initialization :obj:`str`
322 """
323 pass
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: 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:
501 writer.add("{")
502 writer.indent()
504 try:
505 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack)
506 except IndexError:
507 pass
508 else:
509 if isinstance(
510 lookahead,
511 (_ArrStructInitGen.OpenBrace, CArrayLiteral, CStructLiteral),
512 ):
513 writer.add_line("")
514 return formatters
516 @staticmethod
517 def literal_or_value_handler(
518 top,
519 stack: deque,
520 writer: CodeWriterLite,
521 formatters: LiteralFormatters,
522 ) -> LiteralFormatters:
523 writer.add(generate_c_value_initializer(top, formatters))
525 try:
526 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack)
527 except IndexError:
528 pass
529 else:
530 if isinstance(
531 lookahead,
532 (
533 CExtendedLiteral,
534 VariableValue,
535 _ArrStructInitGen.Designator,
536 ),
537 ):
538 writer.add(",")
539 if isinstance(lookahead, (CExtendedLiteral, VariableValue)):
540 writer.add(" ")
541 return formatters
543 @staticmethod
544 def designator_handler(
545 top,
546 stack: deque,
547 writer: CodeWriterLite,
548 formatters: LiteralFormatters,
549 ) -> LiteralFormatters:
550 writer.add_line("." + top.name + " = ")
551 return formatters
553 @staticmethod
554 def formatted_literal_handler(
555 top,
556 stack: deque,
557 writer: CodeWriterLite,
558 formatters: LiteralFormatters,
559 ) -> LiteralFormatters:
560 new_formatters = formatters.replace(top.formatter)
562 stack.append(formatters)
563 stack.append(top.value)
564 stack.append(new_formatters)
565 return formatters
567 @staticmethod
568 def literal_formatters_handler(
569 top,
570 stack: deque,
571 writer: CodeWriterLite,
572 formatters: LiteralFormatters,
573 ) -> LiteralFormatters:
574 formatters = top
575 return formatters
577 TypeActionPair = namedtuple("TypeActionPair", ("types", "action"))
580_ArrStructInitGen.type_action_pairs = (
581 _ArrStructInitGen.TypeActionPair(
582 types=(CArrayLiteral), action=_ArrStructInitGen.c_array_literal_handler
583 ),
584 _ArrStructInitGen.TypeActionPair(
585 types=(CStructLiteral),
586 action=_ArrStructInitGen.c_struct_literal_handler,
587 ),
588 _ArrStructInitGen.TypeActionPair(
589 types=(_ArrStructInitGen.ClosedBrace),
590 action=_ArrStructInitGen.closed_brace_handler,
591 ),
592 _ArrStructInitGen.TypeActionPair(
593 types=(_ArrStructInitGen.OpenBrace),
594 action=_ArrStructInitGen.open_brace_handler,
595 ),
596 _ArrStructInitGen.TypeActionPair(
597 types=(CExtendedLiteral, VariableValue),
598 action=_ArrStructInitGen.literal_or_value_handler,
599 ),
600 _ArrStructInitGen.TypeActionPair(
601 types=(_ArrStructInitGen.Designator),
602 action=_ArrStructInitGen.designator_handler,
603 ),
604 _ArrStructInitGen.TypeActionPair(
605 types=(FormattedLiteral),
606 action=_ArrStructInitGen.formatted_literal_handler,
607 ),
608 _ArrStructInitGen.TypeActionPair(
609 types=(LiteralFormatters),
610 action=_ArrStructInitGen.literal_formatters_handler,
611 ),
612)
615class NoValueError(ValueError):
616 """Variable has no value and hence cannot be initialized."""
619class FuncPtr:
620 """Class describing function pointer args and return type.
622 This class made for declaring function pointer type specifics for use with
623 :class:`Variable`\\ s as their `type` argument.
625 Args:
626 return_type: :obj:`str` containing the return type
627 arguments: an iterable which yields one the following types:
629 * a :class:`Variable`
630 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
631 (`name`, `primitive`)
632 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
633 `primitive`)
634 """
636 __slots__ = ("return_type", "arguments")
638 # TODO unify the creation to be the same as Function
640 def __init__(
641 self, return_type: str, arguments: Iterable = None, comment: str = None
642 ) -> 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, name: str, qualifiers: str = None, array: str = None
653 ) -> str:
654 """Generate the whole declaration :obj:`str` according to parameters.
656 This method is meant to be called from
657 :meth:`Variable.generate_declaration` and
658 :meth:`Variable.generate_initialization` function and is probably
659 useless to You elsewhere.
660 """
661 jointargs = ", ".join(
662 arg.generate_declaration() for arg in self.arguments
663 )
665 retval = "{rt} (*{qual}{name}{arr})({arguments})".format(
666 rt=self.return_type,
667 qual=qualifiers if qualifiers else "",
668 name=name,
669 arguments=jointargs if self.arguments else "",
670 arr=array if array else "",
671 )
673 return retval
676class Variable:
677 """Class describing C variable contruct.
679 You can generate declarations and initializations for variables, and
680 variables can be initialized to very complex values (you can init an array
681 variable to an array of :ccode:`struct` containing arrays that contain
682 :ccode:`structs`, ...).
684 The formatting of initialization :obj:`str`\\ s can be altered by encasing
685 some level of the initialization with a :class:`FormattedLiteral`.
687 As for function values, the following (Python input → C code) translations
688 are assumed (`numpy` and `scipy` `int`\\ s and `float`\\ s are considered ints
689 and floats):
691 - :obj:`dict` → :ccode:`struct` literal
692 - :obj:`list` or `tuple` → array literal
693 - :obj:`int` → :ccode:`int` literal or :ccode:`int` literal
694 - :obj:`float` → :ccode:`float` literal or :ccode:`double` literal
695 - :obj:`bool` → :ccode:`bool` literal or :ccode:`int` literal
696 - :class:`Variable` → name of the variable
698 There is no type checking for initializations. That's what your compiler is
699 for, no sense in replicating such functionality here.
701 Args:
702 name: name of the variable
704 primitive: :obj:`str` name or :class:`FuncPtr` defining the variable's
705 type
707 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
708 variable's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
709 array: :class:`Sequence` of :obj:`int` defining the dimensions of a
710 (possibly multidimensional) array. If it's left out, it's inferred
711 from `value`.
713 comment: accompanying comment
714 value: variable's value, used for initialization. Explained in detail
715 above
717 """
719 __slots__ = (
720 "name",
721 "primitive",
722 "qualifiers",
723 "array",
724 "comment",
725 "value",
726 )
728 def __init__(
729 self,
730 name: str,
731 primitive: Union[str, FuncPtr],
732 qualifiers: Union[Iterable[str], str] = None,
733 array: Iterable = None,
734 comment: str = None,
735 value: CLiteral = None,
736 ) -> None:
738 self.name = assure_str(name)
739 if isinstance(primitive, FuncPtr):
740 self.primitive: Union[FuncPtr, str] = primitive
741 else:
742 self.primitive = assure_str(primitive)
743 self.comment = assure_str(comment) if comment is not None else None
744 self.array = array
745 self.qualifiers = qualifiers
746 self.value = value
748 @staticmethod
749 def merge_formatters(
750 value: CLiteral, formatters: LiteralFormatters = None
751 ) -> Tuple[CLiteral, LiteralFormatters]:
752 if not formatters:
753 formatters = LiteralFormatters()
755 while isinstance(value, FormattedLiteral):
756 formatters = formatters.replace(value.formatter)
757 value = value.value
759 return (value, formatters)
761 def __array_dimensions(self) -> str:
762 value = self.value
763 if value is not None:
764 value, _ = self.merge_formatters(value)
766 if isinstance(self.array, CArrayLiteral):
767 array = "".join(f"[{dim}]" for dim in self.array)
768 elif self.array is not None:
769 array = f"[{self.array}]"
770 elif self.array is None and isinstance(value, str):
771 array = "[]"
772 elif self.array is None and isinstance(value, Iterable):
773 try:
774 val_shape = _shape(value)
775 array = "".join(f"[{dim}]" for dim in val_shape)
776 except ShapelessError:
777 array = ""
778 else:
779 array = ""
781 return array
783 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
784 """Return a :obj:`str` used for initialization of other
785 :obj:`Variable`\\ s"""
786 return self.name
788 def generate_declaration(self, extern: bool = False) -> str:
789 """Return a declaration :obj:`str`.
791 Doesn't end with a semicolon (;).
793 Args:
794 extern: whether or not the value is to be declared as
795 :ccode:`extern`
797 Examples:
798 >>> import numpy as np
799 >>> from csnake import Variable
800 >>> var = Variable(
801 ... "test",
802 ... primitive="int",
803 ... value=np.arange(24).reshape((2, 3, 4))
804 ... )
805 >>> print(var.generate_declaration())
806 int test[2][3][4]
807 """
809 if self.qualifiers is not None:
810 if isinstance(self.qualifiers, str):
811 qual = self.qualifiers + " "
812 else:
813 qual = " ".join(self.qualifiers) + " "
814 else:
815 qual = ""
817 array = self.__array_dimensions()
819 if isinstance(self.primitive, FuncPtr):
820 decl = self.primitive.get_declaration(
821 name=self.name, qualifiers=qual, array=array
822 )
824 return "{ext}{decl}".format(
825 ext="extern " if extern else "", decl=decl
826 )
828 return "{ext}{qual}{prim} {name}{array}".format(
829 ext="extern " if extern else "",
830 qual=qual,
831 prim=self.primitive,
832 name=self.name,
833 array=array,
834 )
836 @property
837 def declaration(self) -> str:
838 """Declaration string.
840 Ends with a semicolon (;).
842 See Also:
843 :meth:`generate_declaration` for the underlying method.
844 """
845 return self.generate_declaration(extern=False) + ";"
847 def __generate_array_struct_initialization(
848 self,
849 array: Union[CArrayLiteral, CStructLiteral],
850 indent: Union[int, str] = 4,
851 writer: CodeWriterLite = None,
852 formatters: LiteralFormatters = None,
853 ) -> None:
854 """Print (multi)dimensional arrays."""
856 if self.value is None:
857 raise NoValueError
859 stack: deque = deque()
860 stack.append(array)
861 if not formatters:
862 formatters = LiteralFormatters()
863 if writer is None:
864 writer = CodeWriterLite(indent=indent)
866 while stack:
867 top = stack.pop()
868 for types, action in _ArrStructInitGen.type_action_pairs:
869 if isinstance(top, types):
870 formatters = action(
871 top=top,
872 stack=stack,
873 writer=writer,
874 formatters=formatters,
875 )
876 break
877 else:
878 raise TypeError("Unknown type on stack")
880 def generate_initialization(
881 self, indent: Union[int, str] = 4
882 ) -> CodeWriterLite:
883 """Return a :class:`CodeWriterLite` instance containing the
884 initialization code for this :class:`Variable`.
886 Ends with a semicolon (;).
888 Args:
889 indent: indent :obj:`str` or :obj:`int` denoting the number of
890 spaces for indentation
893 Example:
894 >>> import numpy as np
895 >>> from csnake import Variable
896 >>> var = Variable(
897 ... "test",
898 ... primitive="int",
899 ... value=np.arange(24).reshape((2, 3, 4))
900 ... )
901 >>> print(var.generate_initialization())
902 int test[2][3][4] = {
903 {
904 {0, 1, 2, 3},
905 {4, 5, 6, 7},
906 {8, 9, 10, 11}
907 },
908 {
909 {12, 13, 14, 15},
910 {16, 17, 18, 19},
911 {20, 21, 22, 23}
912 }
913 };
915 """
917 # main part: generating initializer
918 if self.value is None:
919 raise NoValueError
921 writer = CodeWriterLite(indent=indent)
923 if not isinstance(self.qualifiers, str) and isinstance(
924 self.qualifiers, Iterable
925 ):
926 qual = " ".join(self.qualifiers) + " "
927 elif self.qualifiers is not None:
928 qual = assure_str(self.qualifiers) + " "
929 else:
930 qual = ""
932 array = self.__array_dimensions()
934 if isinstance(self.primitive, FuncPtr):
935 decl = self.primitive.get_declaration(
936 name=self.name, qualifiers=qual, array=array
937 )
938 writer.add_line(decl)
939 else:
940 writer.add_line(f"{qual}{self.primitive} {self.name}{array}")
942 writer.add(" = ")
944 value, formatters = self.merge_formatters(self.value)
946 if isinstance(value, (CArrayLiteral, CStructLiteral)):
947 self.__generate_array_struct_initialization(
948 value, indent, writer, formatters
949 )
950 else:
951 formatters = LiteralFormatters()
952 assignment = generate_c_value_initializer(value, formatters)
953 writer.add(assignment)
955 writer.add(";")
956 return writer
958 @property
959 def initialization(self) -> str:
960 """Initialization :obj:`str`.
962 Ends with a semicolon (;).
964 See Also:
965 :meth:`generate_initialization` for the underlying method.
966 """
967 return str(self.generate_initialization())
969 def __str__(self):
970 """Initialization (if value in not None) or declaration :obj:`str`.
972 Falls back to declaration if :attr:`value` is None.
974 Ends with a semicolon (;).
976 See Also:
977 :meth:`generate_initialization` for the initialization method.
978 :meth:`generate_declaration` for the declaration method.
979 """
980 try:
981 return self.initialization
982 except NoValueError:
983 return self.declaration
986# no VariableValue is also a VariableValue!
987VariableValue.register(Variable)
990def _get_variable(variable: Union[Variable, Collection, Mapping]) -> Variable:
991 """Get a Variable out of one of the following:
993 * a Variable (idempotent)
995 * a Collection (tuple/list-like) of 2 strings (name, primitive)
997 * a Mapping (dict-like) with keys (name, primitive)
998 """
999 if isinstance(variable, Variable):
1000 return variable
1001 elif isinstance(variable, Mapping):
1002 var = Variable(**variable)
1003 return var
1004 elif isinstance(variable, Collection):
1005 if len(variable) != 2:
1006 raise TypeError(
1007 "variable must be a Collection with len(variable) == 2"
1008 )
1009 var = Variable(*variable)
1010 return var
1011 else:
1012 raise TypeError(
1013 "variable must be one of (Variable, Collection, Mapping)"
1014 )
1017class Struct:
1018 """Class describing C :ccode:`struct` construct.
1020 Args:
1021 name: name of struct
1022 typedef: whether or not the struct is :ccode:`typedef`'d
1024 Attributes:
1025 name: name of struct
1026 typedef: whether or not the struct is :ccode:`typedef`'d
1027 variables: :obj:`list` of :ccode:`struct`'s variables
1028 """
1030 __slots__ = ("name", "variables", "typedef")
1032 def __init__(self, name: str, typedef: bool = False) -> None:
1033 self.name = assure_str(name)
1034 self.variables: List[Variable] = []
1035 self.typedef = bool(typedef)
1037 def add_variable(self, variable: Union[Variable, Collection, Mapping]):
1038 """Add a variable to `struct`.
1040 Variables inside of a :class:`Struct` are ordered (added sequentially).
1042 Args:
1043 variable: variable to add. It can be defined in multiple ways.
1045 `variable` can be:
1047 * a :class:`Variable`
1048 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2
1049 :obj:`str`\\ s (`name`, `primitive`)
1050 * a :class:`Mapping` (:obj:`dict`-like) with keys ['name',
1051 'primitive']
1052 """
1054 proc_var = _get_variable(variable)
1055 self.variables.append(proc_var)
1057 def generate_declaration(
1058 self, indent: Union[int, str] = 4
1059 ) -> CodeWriterLite:
1060 """Generate a :class:`CodeWriterLite` instance containing the
1061 initialization code for this :class:`Struct`.
1063 Args:
1064 indent: indent :obj:`str` or :obj:`int` denoting the number of
1065 spaces for indentation
1067 Example:
1068 >>> from csnake import Variable, Struct
1069 >>> strct = Struct("strname", typedef=False)
1070 >>> var1 = Variable("var1", "int")
1071 >>> var2 = Variable("var2", "int", value=range(10))
1072 >>> strct.add_variable(var1)
1073 >>> strct.add_variable(var2)
1074 >>> strct.add_variable(("var3", "int"))
1075 >>> strct.add_variable({"name": "var4", "primitive": "int"})
1076 >>> print(strct.generate_declaration())
1077 struct strname
1078 {
1079 int var1;
1080 int var2[10];
1081 int var3;
1082 int var4;
1083 };
1084 """
1085 writer = CodeWriterLite(indent=indent)
1087 if self.typedef:
1088 writer.add_line("typedef struct")
1089 else:
1090 writer.add_line(f"struct {self.name}")
1092 writer.open_brace()
1093 for var in self.variables:
1094 writer.add_line(var.declaration)
1095 writer.close_brace()
1097 if self.typedef:
1098 writer.add(" " + self.name + ";")
1099 else:
1100 writer.add(";")
1102 return writer
1104 @property
1105 def declaration(self):
1106 """:class:`CodeWriterLite` instance containing the
1107 declaration code for this :class:`Struct`.
1109 See Also:
1110 :meth:`generate_declaration` for the underlying method.
1111 """
1113 return self.generate_declaration()
1115 def __str__(self):
1116 """Generate a :obj:`str` instance containing the
1117 declaration code for this :class:`Struct`."""
1118 return str(self.generate_declaration())
1121class AddressOf(VariableValue):
1122 """Address of (&) VariableValue for variable initialization.
1124 Subclass of :class:`VariableValue` that returns an initialization string
1125 containing the & (address of) used on a value.
1127 Args:
1128 variable: variable to return the address of.
1130 Attributes:
1131 variable: variable to return the address of.
1133 Examples:
1134 >>> from csnake import Variable, AddressOf
1135 >>> var1 = Variable("var1", "int")
1136 >>> addrof_var1 = AddressOf(var1)
1137 >>> var2 = Variable("var2", "int", value=addrof_var1)
1138 >>> print(var2)
1139 int var2 = &var1;
1140 """
1142 def __init__(self, variable: Union[VariableValue]) -> None:
1143 if not isinstance(variable, (VariableValue)):
1144 raise TypeError("variable must be of type VariableValue.")
1145 self.variable = variable
1147 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1148 """Return a :obj:`str` used for initialization of other
1149 :obj:`Variable`\\ s"""
1150 return f"&{generate_c_value_initializer(self.variable, formatters)}"
1152 def __str__(self):
1153 """Initialization string."""
1154 return self.init_target_code()
1157class Dereference(VariableValue):
1158 """Dereference (*) modifier for variable initialization.
1160 Subclass of :class:`VariableValue` that returns an initialization string
1161 containing the * (dereference) used on a value.
1163 Args:
1164 value: value to dereference.
1166 Attributes:
1167 value: value to dereference.
1169 Examples:
1170 >>> from csnake import Variable, Dereference
1171 >>> var1 = Variable("var1", "int")
1172 >>> derefd_var1 = Dereference(var1)
1173 >>> var2 = Variable("var2", "int", value=derefd_var1)
1174 >>> print(var2)
1175 int var2 = *var1;
1176 >>> derefd_number = Dereference(16)
1177 >>> var3 = Variable("var3", "int", value=derefd_number)
1178 >>> print(var3)
1179 int var3 = *16;
1180 """
1182 __slots__ = "value"
1184 def __init__(self, value: Union[CExtendedLiteral, VariableValue]) -> None:
1185 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1186 raise TypeError("value must be of type VariableValue or CLiteral.")
1187 self.value = value
1189 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1190 """Return a :obj:`str` used for initialization of other
1191 :obj:`value`\\ s"""
1192 return f"*{generate_c_value_initializer(self.value, formatters)}"
1194 def __str__(self):
1195 """Initialization string."""
1196 return self.init_target_code()
1199class Typecast(VariableValue):
1200 """Typecast modifier for variable initialization.
1202 Subclass of :class:`VariableValue` that returns an initialization string
1203 containing the typecast to `type` used on a value.
1205 Args:
1206 value: value to be typecast
1207 cast: type to cast to
1209 Attributes:
1210 value: value to be typecast
1211 cast: type to cast to
1213 Examples:
1214 >>> from csnake import Variable, Typecast
1215 >>> var1 = Variable("var1", "int")
1216 >>> cast_var1 = Typecast(var1, 'long')
1217 >>> var2 = Variable("var2", "int", value=cast_var1)
1218 >>> print(var2)
1219 int var2 = (long) var1;
1220 >>> cast_number = Typecast(16, 'long')
1221 >>> var3 = Variable("var3", "int", value=cast_number)
1222 >>> print(var3)
1223 int var3 = (long) 16;
1224 """
1226 __slots__ = ("subscript", "cast")
1228 def __init__(
1229 self, value: Union[CExtendedLiteral, VariableValue], cast: str
1230 ) -> None:
1231 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1232 raise TypeError(
1233 "variable must be of type VariableValue or CLiteral."
1234 )
1235 self.value = value
1236 self.cast = assure_str(cast)
1238 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1239 """Return a :obj:`str` used for initialization of other
1240 :obj:`value`\\ s"""
1241 initializer = generate_c_value_initializer(self.value, formatters)
1242 return f"({self.cast}) {initializer}"
1244 def __str__(self):
1245 """Initialization string."""
1246 return self.init_target_code
1249class Subscript(VariableValue):
1250 """Subscript ([]) modifier for variable initialization.
1252 Subclass of :class:`VariableValue` that returns an initialization string
1253 with a subscripted value.
1255 Args:
1256 variable: variable to be typecast
1257 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or
1258 `tuple` of them representing the subscript[s].
1260 Attributes:
1261 variable: variable to be typecast
1262 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or
1263 `tuple` of them representing the subscript[s].
1265 Examples:
1266 >>> from csnake import Variable, Subscript
1267 >>> var1 = Variable("var1", "int")
1268 >>> subscr1_var1 = Subscript(var1, 3)
1269 >>> var2 = Variable("var2", "int", value=subscr1_var1)
1270 >>> print(var2)
1271 int var2 = var1[3];
1272 >>> subscr2_var1 = Subscript(var1, (3, 2))
1273 >>> var3 = Variable("var3", "int", value=subscr2_var1)
1274 >>> print(var3)
1275 int var3 = var1[3][2];
1276 >>> subscr2_var1 = Subscript(var1, (var3, 2))
1277 >>> var4 = Variable("var4", "int", value=subscr2_var1)
1278 >>> print(var4)
1279 int var4 = var1[var3][2];
1280 """
1282 __slots__ = ("subscript", "variable")
1284 def __init__(
1285 self, variable: Union[CLiteral, VariableValue], subscript: Iterable
1286 ) -> None:
1287 if not isinstance(variable, VariableValue):
1288 raise TypeError("variable must be VariableValue.")
1289 self.variable = variable
1291 if not isinstance(
1292 subscript, (CIntLiteral, Iterable, VariableValue, CArrayLiteral)
1293 ):
1294 raise TypeError(
1295 "Subscript must be an CIntLiteral, VariableValue "
1296 "(or Variable), list or tuple."
1297 )
1299 if not subscript:
1300 raise TypeError("Subscript must be non-empty.")
1302 try:
1303 self.subscript = list(subscript)
1304 except TypeError:
1305 self.subscript = [subscript]
1307 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1308 """Return a :obj:`str` used for initialization of other
1309 :obj:`Variable`\\ s"""
1311 return generate_c_value_initializer(
1312 self.variable, formatters
1313 ) + "".join(
1314 f"[{generate_c_value_initializer(dim, formatters)}]"
1315 for dim in self.subscript
1316 )
1318 def __str__(self):
1319 """Initialization string."""
1320 return self.init_target_code()
1323class Dot(VariableValue):
1324 """Dot (.) VariableValue for variable initialization.
1326 Subclass of :class:`VariableValue` that returns an initialization string
1327 for accessing a specific member of a struct.
1329 Args:
1330 variable: variable whose member we're accessing
1331 member: name of member we're accessing
1333 Attributes:
1334 variable: variable whose member we're accessing
1335 member: name of member we're accessing
1337 Examples:
1338 >>> from csnake import Variable, Dot
1339 >>> var1 = Variable("var1", "struct somestr")
1340 >>> dotvar = Dot(var1, 'some_member')
1341 >>> var2 = Variable("var2", "int", value=dotvar)
1342 >>> print(var2)
1343 int var2 = var1.some_member;
1344 """
1346 __slots__ = ("variable", "member")
1348 def __init__(
1349 self, variable: Union[CLiteral, VariableValue], member: str
1350 ) -> None:
1351 if not isinstance(variable, VariableValue):
1352 raise TypeError("variable must be VariableValue.")
1353 self.variable = variable
1355 if not isinstance(member, (VariableValue, str)):
1356 raise TypeError("member must be either a VariableValue or a str.")
1357 self.member = member
1359 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1360 """Return a :obj:`str` used for initialization of other
1361 :obj:`Variable`\\ s"""
1362 if isinstance(self.member, str):
1363 return (
1364 self.variable.init_target_code(formatters) + "." + self.member
1365 )
1366 elif isinstance(self.member, VariableValue):
1367 return (
1368 self.variable.init_target_code(formatters)
1369 + "."
1370 + self.member.init_target_code(formatters)
1371 )
1373 def __str__(self):
1374 """Initialization string."""
1375 return self.init_target_code()
1378class Arrow(VariableValue):
1379 """Arrow (->) VariableValue for variable initialization.
1381 Subclass of :class:`VariableValue` that returns an initialization string
1382 for accessing a specific member of a struct indirectly (through a pointer).
1384 Args:
1385 variable: variable whose member we're accessing
1386 member: name of member we're accessing
1388 Attributes:
1389 variable: variable whose member we're accessing
1390 member: name of member we're accessing
1392 Examples:
1393 >>> from csnake import Variable, Arrow
1394 >>> var1 = Variable("var1", "struct somestr")
1395 >>> arrvar = Arrow(var1, 'some_member')
1396 >>> var2 = Variable("var2", "int", value=arrvar)
1397 >>> print(var2)
1398 int var2 = var1->some_member;
1399 """
1401 __slots__ = "variable" "item"
1403 def __init__(
1404 self, variable: Union[CLiteral, VariableValue], item: str
1405 ) -> None:
1406 if not isinstance(variable, VariableValue):
1407 raise TypeError("variable must be VariableValue.")
1408 self.variable = variable
1410 if not isinstance(item, (VariableValue, str)):
1411 raise TypeError("item must be either a VariableValue or a str.")
1412 self.item = item
1414 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1415 """Return a :obj:`str` used for initialization of other
1416 :obj:`Variable`\\ s"""
1417 if isinstance(self.item, str):
1418 return (
1419 self.variable.init_target_code(formatters) + "->" + self.item
1420 )
1421 elif isinstance(self.item, VariableValue):
1422 return (
1423 self.variable.init_target_code(formatters)
1424 + "->"
1425 + self.item.init_target_code(formatters)
1426 )
1428 def __str__(self):
1429 """Initialization string."""
1430 return self.init_target_code()
1433class GenericModifier(VariableValue):
1434 """VariableValue generated by applying a function to a value.
1436 A value's initializer string is passed through the supplied function, which
1437 should return a :obj:`str`.
1439 Args:
1440 value: value whose initializer we're passing through a function
1441 format_function: function used for modifying a value's initializer
1443 Attributes:
1444 value: value whose initializer we're passing through a function
1445 format_function: function used for modifying a value's initializer
1447 Examples:
1448 >>> from csnake import Variable, GenericModifier
1449 >>> var1 = Variable("var1", "int")
1450 >>> genmod_var1 = GenericModifier(var1, lambda l: f'TRANSLATE({l})')
1451 >>> var2 = Variable("var2", "int", value=genmod_var1)
1452 >>> print(var2)
1453 int var2 = TRANSLATE(var1);
1454 >>> genmod_var2 = GenericModifier('test', lambda l: f'do_whatever({l})')
1455 >>> var3 = Variable("var3", "int", value=genmod_var2)
1456 >>> print(var3)
1457 int var3 = do_whatever("test");
1458 """
1460 __slots__ = ("value", "format_function")
1462 def __init__(
1463 self, value: Union[CExtendedLiteral, VariableValue], function: Callable
1464 ) -> None:
1465 if not isinstance(value, (CExtendedLiteral, VariableValue)):
1466 raise TypeError(
1467 "value must be of type VariableValue or CExtendedLiteral."
1468 )
1469 self.value = value
1470 self.format_function = function
1472 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1473 """Return a :obj:`str` used for initialization of other
1474 :obj:`Variable`\\ s"""
1475 return self.format_function(generate_c_value_initializer(self.value))
1477 def __str__(self):
1478 """Initialization string."""
1479 return self.init_target_code()
1482class OffsetOf(VariableValue):
1483 """offsetof VariableValue modifier for initializing to offsets of struct
1484 members.
1486 Subclass of :class:`VariableValue` that returns an initialization string
1487 containing the offset of a specific member of a struct.
1490 Args:
1491 struct: struct whose member's offset we're using
1492 member: struct member whose offset we're using
1494 Attributes:
1495 struct: struct whose member's offset we're using
1496 member: struct member whose offset we're using
1498 Examples:
1499 >>> from csnake import Variable, OffsetOf, Struct
1500 >>> offs_val1 = OffsetOf('struct some', 'some_member')
1501 >>> var2 = Variable("var2", "int", value=offs_val1 )
1502 >>> print(var2)
1503 int var2 = offsetof(struct some, some_member);
1504 >>> test_struct = Struct('other')
1505 >>> offs_val2 = OffsetOf(test_struct, 'other_member')
1506 >>> var3 = Variable("var3", "int", value=offs_val2 )
1507 >>> print(var3)
1508 int var3 = offsetof(struct other, other_member);
1509 """
1511 __slots__ = ("struct", "member")
1513 def __init__(
1514 self, struct: Union[Struct, str], member: Union[VariableValue, str]
1515 ) -> None:
1516 if not isinstance(struct, (str, Struct)):
1517 raise TypeError("First argument must be either a Struct or a str.")
1518 self.struct = struct
1520 if not isinstance(member, (VariableValue, str)):
1521 raise TypeError(
1522 "Second argument must be either a VariableValue or a str"
1523 )
1524 self.member = member
1526 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1527 """Return a :obj:`str` used for initialization of other
1528 :obj:`Variable`\\ s"""
1529 if isinstance(self.struct, str):
1530 struct_name = self.struct
1531 elif isinstance(self.struct, Struct):
1532 if self.struct.typedef:
1533 struct_name = self.struct.name
1534 else:
1535 struct_name = "struct " + self.struct.name
1537 if isinstance(self.member, str):
1538 member_name = self.member
1539 elif isinstance(self.member, VariableValue):
1540 member_name = self.member.init_target_code(formatters)
1542 return f"offsetof({struct_name}, {member_name})"
1544 def __str__(self):
1545 """Initialization string."""
1546 return self.init_target_code()
1549class TextModifier(VariableValue):
1550 """Generic textual VariableValue for initializing to an arbitrary
1551 :obj:`str`.
1553 The :obj:`str` supplied as the argument is output verbatim as the
1554 Initialization string.
1556 Args:
1557 text: initialization string
1559 Attributes:
1560 text: initialization string
1562 Examples:
1563 >>> from csnake import Variable, TextModifier
1564 >>> textmod1 = TextModifier('whatever + you + want')
1565 >>> var2 = Variable("var2", "int", value=textmod1)
1566 >>> print(var2)
1567 int var2 = whatever + you + want;
1568 >>> textmod2 = TextModifier(f'you_can_do_this_too + {var2.name}')
1569 >>> var3 = Variable("var3", "int", value=textmod2)
1570 >>> print(var3)
1571 int var3 = you_can_do_this_too + var2;
1572 """
1574 __slots__ = "text"
1576 def __init__(self, text: str) -> None:
1577 self.text = assure_str(text)
1579 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1580 """Return a :obj:`str` used for initialization of other
1581 :obj:`Variable`\\ s"""
1582 return self.text
1584 def __str__(self):
1585 """Initialization string."""
1586 return self.init_target_code()
1589class Function:
1590 """Class describing C function.
1592 You can generate a function prototype (declaration), a definition or a
1593 call.
1594 A function's body is a :class:`CodeWriterLite`, so You can add lines of
1595 code to it (or a whole new :class:`CodeWriter` instance).
1597 Args:
1598 name: name of the function
1599 return_type: :obj:`str` containing function's return type
1601 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
1602 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
1604 arguments: an iterable which yields one the following types:
1606 * a :class:`Variable`
1607 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1608 (`name`, `primitive`)
1609 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1610 `primitive`)
1613 Attributes:
1614 name: name of the function
1615 return_type: :obj:`str` containing function's return type
1617 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the
1618 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...)
1620 arguments: an iterable which yields one the following types:
1622 * a :class:`Variable`
1623 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1624 (`name`, `primitive`)
1625 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1626 `primitive`)
1628 codewriter: internal instance of :class:`CodeWriterLite` that contains
1629 the funtion's body code.
1630 """
1632 __slots__ = (
1633 "name",
1634 "return_type",
1635 "arguments",
1636 "qualifiers",
1637 "codewriter",
1638 )
1640 def __init__(
1641 self,
1642 name: str,
1643 return_type: str = "void",
1644 qualifiers: Union[str, Iterable[str]] = None,
1645 arguments: Iterable = None,
1646 ) -> None:
1647 self.name = assure_str(name)
1648 self.return_type = assure_str(return_type)
1649 self.arguments: List[Variable] = []
1650 self.codewriter = CodeWriterLite()
1652 if isinstance(qualifiers, str):
1653 self.qualifiers = qualifiers.split()
1654 elif qualifiers is not None:
1655 self.qualifiers = [
1656 assure_str(qualifier) for qualifier in qualifiers
1657 ]
1658 else:
1659 self.qualifiers = []
1661 if arguments is not None:
1662 for arg in arguments:
1663 proc_arg = _get_variable(arg)
1664 self.add_argument(proc_arg)
1666 def add_argument(self, arg: Variable) -> None:
1667 """Add an argument to function.
1669 Arguments are added sequentially.
1671 arg: an one the following types:
1673 * a :class:`Variable`
1674 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1675 (`name`, `primitive`)
1676 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1677 `primitive`)
1678 """
1680 proc_arg = _get_variable(arg)
1682 self.arguments.append(proc_arg)
1684 def add_arguments(self, args: Iterable[Variable]) -> None:
1685 """Add multiple arguments to function.
1687 Arguments are added sequentially.
1689 args: an iterable which yields one the following types:
1691 * a :class:`Variable`
1692 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings
1693 (`name`, `primitive`)
1694 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`,
1695 `primitive`)
1696 """
1698 for arg in args:
1699 self.add_argument(arg)
1701 def add_code(self, code: Union[str, Iterable[str]]) -> None:
1702 """Add a :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to function's body
1704 Since a :class:`CodeWriter` is an iterable of :obj:`str`\\ s, you can
1705 simply add its contents to the function by passing it to this method.
1708 Args:
1709 code: :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to be added
1710 """
1712 self.codewriter.add_lines(code)
1714 def generate_call(self, *arg) -> str:
1715 """Return function calling code for specific arguments.
1717 Args:
1718 \\*arg: argument values for call, in order of appearance.
1720 Doesn't end with a semicolon (;).
1722 Examples:
1723 >>> from csnake import Variable, Function
1724 >>> arg1 = Variable("arg1", "int")
1725 >>> arg2 = Variable("arg2", "int", value=range(10))
1726 >>> arg3 = ("arg3", "int")
1727 >>> arg4 = {"name": "arg4", "primitive": "int"}
1728 >>> fun = Function(
1729 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1730 ... )
1731 >>> fun.add_code(("code;", "more_code;"))
1732 >>> print(fun.generate_call(1, 2, 3, 4))
1733 testfunct(1, 2, 3, 4)
1734 """
1736 # if the arguments are all given in a single iterable
1737 if len(arg) == 1 and isinstance(next(iter(arg)), Iterable):
1738 return self.generate_call(*next(iter(arg)))
1740 if not len(arg) == len(self.arguments):
1741 raise ValueError("number of arguments must match")
1743 arg_str = ", ".join(str(argument) for argument in arg)
1744 return f"{self.name}({arg_str})"
1746 def generate_prototype(self, extern: bool = False) -> str:
1747 """Generate function prototype code.
1749 Args:
1750 extern: whether or not the function is to be declared as
1751 :ccode:`extern`
1753 Doesn't end with a semicolon (;).
1755 Examples:
1756 >>> from csnake import Variable, Function
1757 >>> arg1 = Variable("arg1", "int")
1758 >>> arg2 = Variable("arg2", "int", value=range(10))
1759 >>> arg3 = ("arg3", "int")
1760 >>> arg4 = {"name": "arg4", "primitive": "int"}
1761 >>> fun = Function(
1762 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1763 ... )
1764 >>> fun.add_code(("code;", "more_code;"))
1765 >>> print(fun.generate_prototype())
1766 void testfunct(int arg1, int arg2[10], int arg3, int arg4)
1767 """
1768 return "{extern}{qual}{ret} {nm}({args})".format(
1769 extern="extern " if extern else "",
1770 qual=" ".join(self.qualifiers) + " " if self.qualifiers else "",
1771 ret=self.return_type,
1772 nm=self.name,
1773 args=", ".join(
1774 var.generate_declaration() for var in self.arguments
1775 )
1776 if self.arguments
1777 else "void",
1778 )
1780 @property
1781 def prototype(self) -> str:
1782 """Function prototype (declaration) :obj:`str`.
1784 Ends with a semicolon (;).
1786 See Also:
1787 :meth:`generate_prototype` for the underlying method.
1788 """
1789 return self.generate_prototype() + ";"
1791 def generate_definition(
1792 self, indent: Union[int, str] = " "
1793 ) -> CodeWriterLite:
1794 """Return a :class:`CodeWriterLite` instance containing the
1795 definition code for this :class:`Function`.
1797 Args:
1798 indent: indent :obj:`str` or :obj:`int` denoting the number of
1799 spaces for indentation
1801 Examples:
1802 >>> from csnake import Variable, Function
1803 >>> arg1 = Variable("arg1", "int")
1804 >>> arg2 = Variable("arg2", "int", value=range(10))
1805 >>> arg3 = ("arg3", "int")
1806 >>> arg4 = {"name": "arg4", "primitive": "int"}
1807 >>> fun = Function(
1808 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
1809 ... )
1810 >>> fun.add_code(("code;", "more_code;"))
1811 >>> print(fun.generate_definition())
1812 void testfunct(int arg1, int arg2[10], int arg3, int arg4)
1813 {
1814 code;
1815 more_code;
1816 }
1818 """
1819 writer = CodeWriterLite(indent=indent)
1820 writer.add_line(self.generate_prototype())
1821 writer.open_brace()
1823 writer.add_lines(self.codewriter) # type: ignore
1825 writer.close_brace()
1827 return writer
1829 @property
1830 def definition(self) -> str:
1831 """Function definition :obj:`str`.
1833 See Also:
1834 :meth:`generate_definition` for the underlying method.
1835 """
1836 return self.generate_definition().code
1838 def init_target_code(self, formatters: LiteralFormatters = None) -> str:
1839 """Return code used for variable initialization, formatted with the
1840 supplied formatters.
1842 Args:
1843 formatters: collection of formatters used for formatting the
1844 initialization :obj:`str`
1845 """
1846 return self.name
1849# TODO: allow for functions to be used to initialize function pointers
1851VariableValue.register(Function)
1854class Enum:
1855 """Class describing C :ccode:`enum` construct.
1857 Args:
1858 name: name of the enum
1859 prefix: prefix to add to every enumeration's name
1860 typedef: whether or not the enum is :ccode:`typedef`'d
1862 Attributes:
1863 name: name of the enum
1864 typedef: whether or not the enum is :ccode:`typedef`'d
1865 prefix: prefix to add to every enumeration's name
1866 values: `list` of :class:`Enum.EnumValue`
1867 """
1869 __slots__ = ("typedef", "values", "name", "prefix")
1871 class EnumValue:
1872 """Singular value of an C-style enumeration."""
1874 __slots__ = ("name", "value", "comment")
1876 def __init__(
1877 self,
1878 name: str,
1879 value: Union[CLiteral, VariableValue] = None,
1880 comment: str = None,
1881 ) -> None:
1882 self.name = assure_str(name)
1883 if value is not None and not isinstance(
1884 value, (CLiteral, VariableValue)
1885 ):
1886 raise ValueError(
1887 f"value ({value}) must be one of (CLiteral, VariableValue)"
1888 )
1889 self.value = value
1890 self.comment = assure_str(comment) if comment is not None else None
1892 def __init__(
1893 self, name: str, prefix: str = None, typedef: bool = False
1894 ) -> None:
1896 self.typedef = bool(typedef)
1897 # enum values
1898 self.values: List[Enum.EnumValue] = []
1899 self.name = assure_str(name)
1901 if prefix is not None:
1902 self.prefix = assure_str(prefix)
1903 else:
1904 self.prefix = ""
1906 def add_value(
1907 self,
1908 name: str,
1909 value: Union[CLiteral, VariableValue] = None,
1910 comment: str = None,
1911 ) -> None:
1912 """Add a single :ccode:`name = value` pair (or just :ccode:`name`)."""
1913 self.values.append(self.EnumValue(name, value=value, comment=comment))
1915 def add_values(
1916 self,
1917 values: Union[Mapping, Collection[Union[Mapping, Collection, str]]],
1918 ) -> None:
1919 """Add multiple :ccode:`name = value` pairs (or just :ccode:`name`\\ s).
1921 Values can be one of:
1923 * a :obj:`dict` with `name` : `value` entries
1924 * a collection of:
1925 * :obj:`str`\\ s denoting names (no value)
1926 * :obj:`dict`\\ s with keys [`name`, `value`, `comment`], and optional
1927 keys [`value`, `comment`]
1928 * collections (:obj:`list`-like) of length 1-3 denoting [`name`,
1929 `value`, `comment`] respectively
1931 """
1932 if isinstance(values, Mapping):
1933 for name, value in values.items():
1934 self.add_value(name, value)
1936 else:
1937 for value in values:
1938 if isinstance(value, str):
1939 self.add_value(value)
1940 elif isinstance(value, Mapping):
1941 self.add_value(**value)
1942 # lists and tuples
1943 else:
1944 defaults = (None, None, None)
1945 name = seq_get(value, defaults, 0)
1946 value = seq_get(value, defaults, 1)
1947 comment = seq_get(value, defaults, 2)
1949 name = assure_str(name)
1951 self.add_value(name, value, comment)
1953 def generate_declaration(
1954 self, indent: Union[int, str] = 4
1955 ) -> CodeWriterLite:
1956 """Generate enum declaration code.
1958 Generate a :class:`CodeWriterLite` instance containing the
1959 initialization code for this :class:`Enum`.
1961 Args:
1962 indent: indent :obj:`str` or :obj:`int` denoting the number of
1963 spaces for indentation
1966 Examples:
1967 >>> from csnake import (
1968 ... Enum, Variable, Dereference, AddressOf
1969 ... )
1970 >>> name = "somename"
1971 >>> pfx = "pfx"
1972 >>> typedef = False
1973 >>> enum = Enum(name, prefix=pfx, typedef=typedef)
1974 >>> cval1 = Variable("varname", "int")
1975 >>> enum.add_value("val1", 1)
1976 >>> enum.add_value("val2", Dereference(1000))
1977 >>> enum.add_value("val3", cval1)
1978 >>> enum.add_value("val4", AddressOf(cval1), "some comment")
1979 >>> print(enum.generate_declaration())
1980 enum somename
1981 {
1982 pfxval1 = 1,
1983 pfxval2 = *1000,
1984 pfxval3 = varname,
1985 pfxval4 = &varname /* some comment */
1986 };
1987 """
1989 gen = CodeWriterLite(indent=indent)
1991 if self.typedef:
1992 gen.add_line("typedef enum")
1993 else:
1994 gen.add_line("enum {name}".format(name=self.name))
1995 gen.open_brace()
1997 def _generate_lines(
1998 values: Iterable, lastline: bool = False
1999 ) -> Iterator:
2000 return (
2001 (
2002 "{prefix}{name}{value}{comma}".format(
2003 prefix=self.prefix,
2004 name=assure_str(val.name),
2005 value=(" = " + generate_c_value_initializer(val.value))
2006 if val.value
2007 else "",
2008 comma="," if not lastline else "",
2009 ),
2010 val.comment,
2011 )
2012 for val in values
2013 )
2015 commalines = _generate_lines(self.values[:-1], lastline=False)
2016 lastline = _generate_lines(self.values[-1:], lastline=True)
2018 for line in chain(commalines, lastline):
2019 gen.add_line(*line)
2021 gen.close_brace()
2023 if self.typedef:
2024 gen.add(" " + self.name + ";")
2025 else:
2026 gen.add(";")
2028 return gen
2030 @property
2031 def declaration(self) -> str:
2032 """The declaration :obj:`str` (with indentation of 4 spaces).
2035 See Also:
2036 :meth:`generate_declaration` for the underlying method.
2037 """
2038 return self.generate_declaration().code
2040 def __str__(self) -> str:
2041 """
2042 Return the declaration :obj:`str` (with indentation of 4 spaces).
2043 """
2044 return self.declaration