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 -*-
2import re
3from datetime import date
4from pathlib import Path
5from typing import Iterable
6from typing import List
7from typing import Mapping
8from typing import Optional
9from typing import Union
11from ._version import __version__
12from .cconstructs import CExtendedLiteral
13from .cconstructs import Enum
14from .cconstructs import Function
15from .cconstructs import Struct
16from .cconstructs import Variable
17from .cconstructs import VariableValue
18from .cconstructs import generate_c_value_initializer
19from .codewriterlite import CodeWriterLite
20from .utils import assure_str
22SOURCE_URL = "https://gitlab.com/andrejr/csnake"
23PYPI_URL = "https://pypi.org/project/csnake"
26class DefStackEmptyError(IndexError):
27 "Ended a if[n]def that wasn't started earlier. To ignore, use \
28 ignore_ifdef_stack=True as parameter"
31class SwitchStackEmptyError(IndexError):
32 "Ended a switch that wasn't started earlier. To ignore, use \
33 ignore_switch_stack=True as parameter"
36class CodeWriter(CodeWriterLite):
37 """Container class for C code, with methods to add C constructs.
39 :class:`CodeWriter` (abbreviated `CW` or `cw`) is a code container that
40 allows you to add code (both manually written and generated), access it as
41 a :obj:`str`, iterate over the lines of code and output the code to a file.
43 It also helps you to indent and dedent code, open or close code blocks
44 consistently.
46 :class:`CodeWriter` is iterable (iterates over lines of code), and also has
47 methods like :meth:`__contains__` and :meth:`__len__`.
49 Here's an overview of abc's :class:`CodeWriter` is a virtual subclass of:
51 >>> from csnake import CodeWriter
52 >>> from collections.abc import Container, Iterable, Sized, Collection
53 >>> cwr = CodeWriter()
54 >>> assert isinstance(cwr, Container)
55 >>> assert isinstance(cwr, Iterable)
56 >>> assert isinstance(cwr, Sized)
57 >>> assert isinstance(cwr, Collection)
59 Args:
60 lf: linefeed character used to separate lines of code when
61 rendering code with :func:`str` or :attr:`code`.
62 indent: indentation unit string or :class:`int` number of spaces,
63 used to indent code.
65 Attributes:
67 lf(str): linefeed character used to separate lines of code when
68 rendering code with :func:`str` or :attr:`code`.
70 lines(list(str)): lines of code.
71 """
73 __slots__ = ("_def_stack", "_switch_stack")
75 _CPP = "__cplusplus"
77 def __init__(
78 self, lf: Optional[str] = None, indent: Union[int, str] = 4
79 ) -> None:
80 super().__init__(lf, indent)
82 # initialize values
83 self._def_stack: List[str] = []
84 self._switch_stack: List[str] = []
86 def add_autogen_comment(
87 self, source_file_name: Optional[str] = None
88 ) -> None:
89 """Add a comment denoting the code was autogenerated by a script.
91 File name of the script the file is generated from is optional.
93 Args:
94 source_file_name: name of the script the code was generated from
96 Examples:
97 Without a script file name:
99 >>> from csnake import CodeWriter
100 >>> cwr1 = CodeWriter()
101 >>> cwr1.add_autogen_comment()
102 >>> print(cwr1) # doctest: +SKIP
103 /*
104 * This file was automatically generated using csnake v0.2.1.
105 *
106 * This file should not be edited directly, any changes will be
107 * overwritten next time the script is run.
108 *
109 * Source code for csnake is available at:
110 * https://gitlab.com/andrejr/csnake
111 */
113 With a script name:
115 >>> from csnake import CodeWriter
116 >>> cwr2 = CodeWriter()
117 >>> cwr2.add_autogen_comment('test.py')
118 >>> print(cwr2) # doctest: +SKIP
119 /*
120 * This file was automatically generated using csnake v0.2.1.
121 *
122 * This file should not be edited directly, any changes will be
123 * overwritten next time the script is run.
124 * Make any changes to the file 'test.py', not this file.
125 *
126 * Source code for csnake is available at:
127 * https://gitlab.com/andrejr/csnake
128 */
129 """
130 self.start_comment()
131 self.add_line(
132 f"This file was automatically generated using csnake v{__version__}."
133 )
134 self.add_line()
135 self.add_lines(
136 (
137 "This file should not be edited directly, any changes will be",
138 "overwritten next time the script is run.",
139 )
140 )
142 if source_file_name:
143 source_file_name = assure_str(source_file_name)
144 if source_file_name:
145 self.add_line(
146 f"Make any changes to the file {source_file_name!r}, not this file."
147 )
148 self.add_line()
149 self.add_line("Source code for csnake is available at:")
150 self.add_line(SOURCE_URL)
152 if PYPI_URL:
153 self.add_line()
154 self.add_line("csnake is also available on PyPI, at :")
155 self.add_line(f"{PYPI_URL}")
156 self.end_comment()
158 def add_license_comment(
159 self,
160 license_: str,
161 authors: Optional[Iterable[Mapping[str, str]]] = None,
162 intro: Optional[str] = None,
163 year: Optional[int] = None,
164 ) -> None:
165 """Add a comment with the license and authors.
167 The comment also adds the year to the copyright.
169 Args:
170 license\\_: :obj:`str` containing the text of the license
171 authors: optional iterable containing mappings (dict-likes, one per
172 author) with key-value pairs for keys 'name' (author's name)
173 and email (author's email, optional)
175 intro: introductory text added before the author list, optional
176 year: year of the copyright (optional). If it is left out, current
177 year is assumed.
179 Raises:
180 ValueError: if any of the arguments is of wrong type
182 Examples:
183 Just license:
185 >>> from csnake import CodeWriter
186 >>> license_text = 'license\\ntext\\nlines'
187 >>> cw1 = CodeWriter()
188 >>> cw1.add_license_comment(license_text)
189 >>> print(cw1)
190 /*
191 * license
192 * text
193 * lines
194 */
196 With introduction:
198 >>> intro_text = 'intro\\ntext'
199 >>> cw2 = CodeWriter()
200 >>> cw2.add_license_comment(license_text, intro=intro_text)
201 >>> print(cw2)
202 /*
203 * intro
204 * text
205 *
206 * license
207 * text
208 * lines
209 */
211 With authors (and year; year defaults to current year):
213 >>> authors = [
214 ... {'name': 'Author Surname'},
215 ... {'name': 'Other Surname', 'email': 'test@email'},
216 ... ]
217 >>> cw3 = CodeWriter()
218 >>> cw3.add_license_comment(
219 ... license_text, authors=authors, intro=intro_text, year=2019
220 ... )
221 >>> print(cw3)
222 /*
223 * intro
224 * text
225 *
226 * Copyright © 2019 Author Surname
227 * Copyright © 2019 Other Surname <test@email>
228 *
229 * license
230 * text
231 * lines
232 */
233 """
234 self.start_comment()
236 if intro:
237 for line in intro.splitlines():
238 if line == "":
239 self.add_line()
240 else:
241 self.add_line(line)
242 self.add_line()
244 year = int(year or date.today().year)
246 if authors:
247 for author in authors:
248 self.add_line(
249 "Copyright © {year} {name}{email}".format(
250 year=year,
251 name=assure_str(author["name"]),
252 email=" <{}>".format(assure_str(author["email"]))
253 if author.get("email", None)
254 else "",
255 )
256 )
257 self.add_line()
259 if not isinstance(license_, str):
260 raise TypeError("license_ must be a string.")
262 for line in license_.splitlines():
263 self.add_line(line)
265 self.end_comment()
267 def add_define(
268 self,
269 name: str,
270 value: Optional[Union[CExtendedLiteral, VariableValue]] = None,
271 comment: Optional[str] = None,
272 ) -> None:
273 """Add a define directive for macros.
275 Macros may or may not have a value.
277 Args:
278 name: macro name
279 value: literal or variable assigned to the macro (optional)
280 comment: comment accompanying macro definition
282 Examples:
283 >>> from csnake import CodeWriter, Variable, Subscript
284 >>> cwr = CodeWriter()
285 >>> cwr.add_define('PI', 3.14)
286 >>> cwr.add_define('LOG')
287 >>> somearr = Variable('some_array', 'int', value=range(0, 5))
288 >>> cwr.add_define('DEST', Subscript(somearr, 2))
289 >>> print(cwr)
290 #define PI 3.14
291 #define LOG
292 #define DEST some_array[2]
294 Todo:
296 - Make a class (within the :mod:`cconstructs`) representing a
297 macro, so that object-type defines may be used as initializers.
298 - Add support for function-like macros.
299 """
300 name = assure_str(name)
301 line = "#define {name}{value}".format(
302 name=str(name),
303 value=" " + generate_c_value_initializer(value)
304 if value is not None
305 else "",
306 )
308 self.add_line(line, comment=comment, ignore_indent=True)
310 def start_if_def(
311 self, define: str, invert: bool = False, comment: Optional[str] = None
312 ) -> None:
313 """
314 Start an :ccode:`#ifdef` or :ccode:`#ifndef` (preprocessor) block.
316 :ccode:`#ifdef` (or :ccode:`#ifndef`) blocks can be nested.
317 :ccode:`#endif` always ends the innermost block. :ccode:`endif`
318 statements are added by :meth:`end_if_def`.
320 Args:
321 define: name of the macro whose existence we're checking.
323 invert: (optional) whether this block is an :ccode:`#ifndef`
324 (:code:`True`) or :ccode:`#ifdef` (:code:`False`, default).
326 comment: (optional) comment accompanying the statement.
328 Raises:
329 ValueError: if one of the arguments is of the wrong type.
331 Examples:
332 :meth:`start_if_def` and :meth:`end_if_def` in action, including
333 nested ifdefs:
335 >>> from csnake import CodeWriter
336 >>> cwr = CodeWriter()
337 >>> cwr.start_if_def('DEBUG')
338 >>> cwr.start_if_def('ARM', invert=True)
339 >>> cwr.add_define('LOG')
340 >>> cwr.end_if_def()
341 >>> cwr.end_if_def()
342 >>> print(cwr)
343 #ifdef DEBUG
344 #ifndef ARM
345 #define LOG
346 #endif /* ARM */
347 #endif /* DEBUG */
348 """
349 self._def_stack.append(define)
351 define = assure_str(define)
352 if invert:
353 self.add_line(
354 f"#ifndef {define}", comment=comment, ignore_indent=True
355 )
356 else:
357 self.add_line(
358 f"#ifdef {define}", comment=comment, ignore_indent=True
359 )
361 def end_if_def(self, ignore_ifdef_stack: bool = False) -> None:
362 """
363 Insert an :ccode:`#endif` to end a :ccode:`#ifdef` (preprocessor) block.
365 :ccode:`#ifdef` (or :ccode:`#ifndef`) blocks can be nested.
366 :ccode:`#endif` always ends the innermost block. :ccode:`endif`
367 statements are added by :meth:`end_if_def`.
369 Args:
370 ignore_ifdef_stack: (optional) don't throw an exception
371 :ccode:`#endif` if is unmatched.
373 Raises:
374 ValueError: if one of the arguments is of the wrong type.
376 DefStackEmptyError: if there isn't a matching :code:`#ifdef` and
377 :obj:`ignore_ifdef_stack` isn't set.
379 Examples:
380 See :meth:`start_if_def`.
381 """
383 try:
384 def_name = self._def_stack.pop()
385 except IndexError as e:
386 if ignore_ifdef_stack:
387 def_name = ""
388 else:
389 raise DefStackEmptyError from e
391 self.add_line("#endif", comment=def_name, ignore_indent=True)
393 def cpp_entry(self) -> None:
394 """Start a conditional :ccode:`extern "C"` for use CPP compilers.
396 Examples:
397 :meth:`cpp_entry` and :meth:`cpp_exit` in action:
399 >>> from csnake import CodeWriter
400 >>> cwr = CodeWriter()
401 >>> cwr.cpp_entry()
402 >>> cwr.add_line('some_code();')
403 >>> cwr.cpp_exit()
404 >>> print(cwr)
405 #ifdef __cplusplus
406 extern "C" {
407 #endif /* __cplusplus */
408 some_code();
409 #ifdef __cplusplus
410 }
411 #endif /* __cplusplus */
412 """
413 self.start_if_def(self._CPP)
414 self.add_line('extern "C" {', ignore_indent=True)
415 self.end_if_def()
417 def cpp_exit(self) -> None:
418 """End a conditional :ccode:`extern "C"` for use CPP compilers.
420 Examples:
421 See :meth:`cpp_entry`.
422 """
423 self.start_if_def(self._CPP)
424 self.add_line("}", ignore_indent=True)
425 self.end_if_def()
427 def start_switch(
428 self, switch: Union[CExtendedLiteral, VariableValue]
429 ) -> None:
430 """Start a switch statement.
432 Used with :meth:`end_switch`, :meth:`add_switch_case`,
433 :meth:`add_switch_default`, :meth:`add_switch_break`,
434 :meth:`add_switch_return`, to form C switch statements.
436 Switch statements can be nested, so :meth:`end_switch` closes the
437 innermost switch.
439 Args:
440 switch: literal or variable the choice depends on.
442 Examples:
443 Demonstration of switch-related methods:
445 >>> from csnake import CodeWriter, Variable
446 >>> cw = CodeWriter()
447 >>> var = Variable("somevar", "int")
448 >>> case_var = Variable("case_var", "int")
449 >>> cw.start_switch(var)
450 >>> cw.add_switch_case(2)
451 >>> cw.add_line('do_something();')
452 >>> cw.add_switch_break()
453 >>> cw.add_switch_case(case_var)
454 >>> cw.add_switch_return(5)
455 >>> cw.add_switch_default()
456 >>> cw.add_switch_return(8)
457 >>> cw.end_switch()
458 >>> print(cw)
459 switch (somevar)
460 {
461 case 2:
462 do_something();
463 break;
464 case case_var:
465 return 5;
466 default:
467 return 8;
468 } /* ~switch (somevar) */
469 """
470 switch_str = generate_c_value_initializer(switch)
471 self._switch_stack.append(switch_str)
472 self.add_line(f"switch ({switch_str})")
473 self.open_brace()
475 def end_switch(self, ignore_switch_stack: bool = False) -> None:
476 """End a switch statement.
478 Used with :meth:`start_switch`, :meth:`add_switch_case`,
479 :meth:`add_switch_default`, :meth:`add_switch_break`,
480 :meth:`add_switch_return`, to form C switch statements.
482 Switch statements can be nested, so :meth:`end_switch` closes the
483 innermost switch.
485 Args:
486 ignore_switch_stack: don't throw an exception if a switch start is
487 missing
489 Raises:
490 SwitchStackEmptyError: if :meth:`end_switch` is called outside of a
491 switch statement, and :obj:`ignore_switch_stack` is :code:`False`.
493 Examples:
494 See :meth:`start_switch`.
495 """
496 self.close_brace()
498 try:
499 switch_name = self._switch_stack.pop()
500 except IndexError as e:
501 if ignore_switch_stack:
502 switch_name = ""
503 else:
504 raise SwitchStackEmptyError from e
506 self.add(f" /* ~switch ({switch_name}) */")
508 def add_switch_case(
509 self,
510 case=Union[CExtendedLiteral, VariableValue],
511 comment: Optional[str] = None,
512 ) -> None:
513 """Add a switch case statement.
515 Used with :meth:`start_switch`, :meth:`end_switch`,
516 :meth:`add_switch_default`, :meth:`add_switch_break`,
517 :meth:`add_switch_return`, to form C switch statements.
519 Args:
520 case: literal or variable representing the current case's value
521 comment: accompanying inline comment
523 Examples:
524 See :meth:`start_switch`.
525 """
526 self.add_line(
527 f"case {generate_c_value_initializer(case)}:", comment=comment
528 )
529 self.indent()
531 def add_switch_default(self, comment: Optional[str] = None) -> None:
532 """Add a switch default statement.
534 Used with :meth:`start_switch`, :meth:`end_switch`,
535 :meth:`add_switch_case`, :meth:`add_switch_break`,
536 :meth:`add_switch_return`, to form C switch statements.
538 Switch statements can be nested, so :meth:`end_switch` closes the
539 innermost switch.
541 Examples:
542 See :meth:`start_switch`.
543 """
544 self.add_line("default:", comment=comment)
545 self.indent()
547 def add_switch_break(self) -> None:
548 """Break a switch case.
550 Used with :meth:`start_switch`, :meth:`end_switch`,
551 :meth:`add_switch_case`, :meth:`add_switch_default`,
552 :meth:`add_switch_return`, to form C switch statements.
554 Examples:
555 See :meth:`start_switch`.
556 """
557 self.add_line("break;")
558 self.dedent()
560 def add_switch_return(
561 self, value: Optional[Union[CExtendedLiteral, VariableValue]] = None
562 ) -> None:
563 """Return inside of a switch statement
565 Used with :meth:`start_switch`, :meth:`end_switch`,
566 :meth:`add_switch_case`, :meth:`add_switch_default`,
567 :meth:`add_switch_break`, to form C switch statements.
569 Args:
570 value: literal or variable representing the value to return
572 Examples:
573 See :meth:`start_switch`.
574 """
575 self.add_line(
576 "return{val};".format(
577 val=" " + generate_c_value_initializer(value) if value else ""
578 )
579 )
580 self.dedent()
582 def include(self, name: str, comment: Optional[str] = None) -> None:
583 """Add an :ccode:`#include` directive.
585 System headers should be surrounded with brackets `(<>)`, while local
586 headers may or may not be surrounded with quotation marks `("")` (the
587 resulting code will have quotation marks surrounding the header's name)
589 Args:
590 name: name of header to include, with or without brackets/quotes.
591 If no brackets/quotes surround the name, quotes are used by
592 default.
594 comment: accompanying inline comment
596 Examples:
597 All types of includes.
599 >>> from csnake import CodeWriter
600 >>> cw = CodeWriter()
601 >>> cw.include('"some_local_header.h"')
602 >>> cw.include('other_local_header.h')
603 >>> cw.include("<string.h>")
604 >>> print(cw)
605 #include "some_local_header.h"
606 #include "other_local_header.h"
607 #include <string.h>
608 """
609 name = str(name)
610 if re.search(r'^(<.*>|".*")$', name):
611 pass
612 else:
613 name = f'"{name}"'
615 self.add_line(
616 "#include {name}".format(name=name),
617 comment=comment,
618 ignore_indent=True,
619 )
621 def add_enum(self, enum: Enum) -> None:
622 """Add an enumeration definition.
624 Args:
625 enum: enum in question
627 See Also:
628 :class:`Enum` for details on the `enum` class
630 Examples:
631 >>> from csnake import (
632 ... CodeWriter, Enum, Variable, Dereference, AddressOf
633 ... )
634 >>> cw = CodeWriter()
635 >>> name = "somename"
636 >>> pfx = "pfx"
637 >>> typedef = False
638 >>> enum = Enum(name, prefix=pfx, typedef=typedef)
639 >>> cval1 = Variable("varname", "int")
640 >>> enum.add_value("val1", 1)
641 >>> enum.add_value("val2", Dereference(1000))
642 >>> enum.add_value("val3", cval1)
643 >>> enum.add_value("val4", AddressOf(cval1), "some comment")
644 >>> cw.add_enum(enum)
645 >>> print(cw)
646 enum somename
647 {
648 pfxval1 = 1,
649 pfxval2 = *1000,
650 pfxval3 = varname,
651 pfxval4 = &varname /* some comment */
652 };
653 """
655 if not isinstance(enum, Enum):
656 raise TypeError('enum must be of type "Enum"')
658 self.add_lines(enum.generate_declaration(self._indent_unit).lines)
660 def add_variable_declaration(
661 self, variable: Variable, extern: bool = False
662 ) -> None:
663 """Add a variable's declaration.
665 Args:
666 variable: variable in question
667 extern: wheter to add the :ccode:`extern` qualifier to the
668 declaration (`True`) or not (`False`, default)
670 See Also:
671 :class:`Variable` for details on the `Variable` class
673 Examples:
674 >>> import numpy as np
675 >>> from csnake import CodeWriter, Variable
676 >>> cw = CodeWriter()
677 >>> var = Variable(
678 ... "test",
679 ... primitive="int",
680 ... value=np.arange(24).reshape((2, 3, 4))
681 ... )
682 >>> cw.add_variable_declaration(var)
683 >>> print(cw)
684 int test[2][3][4];
685 """
687 if not isinstance(variable, Variable):
688 raise TypeError("variable must be of type 'Variable'")
690 self.add_line(
691 variable.generate_declaration(extern) + ";",
692 comment=variable.comment,
693 )
695 def add_variable_initialization(self, variable: Variable) -> None:
696 """Add a variable's initialization.
698 Args:
699 variable: variable in question
701 See Also:
702 :class:`Variable` for details on the `Variable` class
704 Example:
705 >>> import numpy as np
706 >>> from csnake import CodeWriter, Variable
707 >>> cw = CodeWriter()
708 >>> var = Variable(
709 ... "test",
710 ... primitive="int",
711 ... value=np.arange(24).reshape((2, 3, 4))
712 ... )
713 >>> cw.add_variable_initialization(var)
714 >>> print(cw)
715 int test[2][3][4] = {
716 {
717 {0, 1, 2, 3},
718 {4, 5, 6, 7},
719 {8, 9, 10, 11}
720 },
721 {
722 {12, 13, 14, 15},
723 {16, 17, 18, 19},
724 {20, 21, 22, 23}
725 }
726 };
727 """
729 if not isinstance(variable, Variable):
730 raise TypeError("variable must be of type 'Variable'")
732 init_cwr = variable.generate_initialization(self._indent_unit)
733 assert isinstance(init_cwr, Iterable)
734 self.add_lines(init_cwr)
736 def add_struct(self, struct: Struct) -> None:
737 """Add a struct declaration.
739 Args:
740 struct: struct in question
742 See Also:
743 :class:`Struct` for details on the `Struct` class.
745 Example:
746 >>> from csnake import CodeWriter, Variable, Struct
747 >>> cw = CodeWriter()
748 >>> strct = Struct("strname", typedef=False)
749 >>> var1 = Variable("var1", "int")
750 >>> var2 = Variable("var2", "int", value=range(10))
751 >>> strct.add_variable(var1)
752 >>> strct.add_variable(var2)
753 >>> strct.add_variable(("var3", "int"))
754 >>> strct.add_variable({"name": "var4", "primitive": "int"})
755 >>> cw.add_struct(strct)
756 >>> print(cw)
757 struct strname
758 {
759 int var1;
760 int var2[10];
761 int var3;
762 int var4;
763 };
764 """
766 if not isinstance(struct, Struct):
767 raise TypeError("struct must be of type 'Struct'")
769 declaration = struct.generate_declaration(indent=self._indent_unit)
770 assert isinstance(declaration, Iterable) # mypy
771 self.add_lines(declaration)
773 def add_function_prototype(
774 self,
775 func: Function,
776 extern: bool = False,
777 comment: Optional[str] = None,
778 ) -> None:
779 """Add a functions's prototype.
781 Args:
782 func: function in question
783 extern: wheter to add the :ccode:`extern` qualifier to the
784 prototype (`True`) or not (`False`, default)
785 comment: accompanying inline comment
787 See Also:
788 :class:`Function` for details on the `Function` class
790 Examples:
791 >>> from csnake import CodeWriter, Variable, Function
792 >>> cw = CodeWriter()
793 >>> arg1 = Variable("arg1", "int")
794 >>> arg2 = Variable("arg2", "int", value=range(10))
795 >>> arg3 = ("arg3", "int")
796 >>> arg4 = {"name": "arg4", "primitive": "int"}
797 >>> fun = Function(
798 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
799 ... )
800 >>> fun.add_code(("code;", "more_code;"))
801 >>> cw.add_function_prototype(fun)
802 >>> print(cw)
803 void testfunct(int arg1, int arg2[10], int arg3, int arg4);
804 """
806 if not isinstance(func, Function):
807 raise TypeError("func must be of type 'Function'")
809 self.add_line(func.generate_prototype(extern) + ";", comment=comment)
811 def add_function_definition(self, func: Function) -> None:
812 """Add a functions's definition / implementation.
814 Args:
815 func: function in question
817 See Also:
818 :class:`Function` for details on the `Function` class
820 Examples:
821 >>> from csnake import CodeWriter, Variable, Function
822 >>> cw = CodeWriter()
823 >>> arg1 = Variable("arg1", "int")
824 >>> arg2 = Variable("arg2", "int", value=range(10))
825 >>> arg3 = ("arg3", "int")
826 >>> arg4 = {"name": "arg4", "primitive": "int"}
827 >>> fun = Function(
828 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
829 ... )
830 >>> fun.add_code(("code;", "more_code;"))
831 >>> cw.add_function_definition(fun)
832 >>> print(cw)
833 void testfunct(int arg1, int arg2[10], int arg3, int arg4)
834 {
835 code;
836 more_code;
837 }
838 """
839 if not isinstance(func, Function):
840 raise TypeError("Argument func must be of type 'Function'")
842 definition = func.generate_definition(self._indent_unit)
843 assert isinstance(definition, Iterable) # mypy
844 self.add_lines(definition)
846 def add_function_call(self, func: Function, *arg) -> None:
847 """Add a call to a function with listed arguments.
849 Args:
850 func: function in question
851 \\*arg: (rest of the args) function's args in sequence
853 See Also:
854 :class:`Function` for details on the `Function` class
856 Examples:
857 >>> from csnake import CodeWriter, Variable, Function
858 >>> cw = CodeWriter()
859 >>> arg1 = Variable("arg1", "int")
860 >>> arg2 = Variable("arg2", "int", value=range(10))
861 >>> arg3 = ("arg3", "int")
862 >>> arg4 = {"name": "arg4", "primitive": "int"}
863 >>> fun = Function(
864 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4)
865 ... )
866 >>> fun.add_code(("code;", "more_code;"))
867 >>> cw.add_function_call(fun, 1, 2, 3, 4)
868 >>> print(cw)
869 testfunct(1, 2, 3, 4);
870 """
872 if not isinstance(func, Function):
873 raise TypeError("func must be of type 'Function'")
875 self.add_line(func.generate_call(*arg) + ";")
877 def write_to_file(self, filename: Union[str, Path]) -> None:
878 """Write code to filename.
880 Args:
881 filename: name of the file to write code into
882 """
883 with Path(filename).open("w") as openfile:
884 openfile.write(self.code)