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