Hide keyboard shortcuts

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 

8 

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 

19 

20SOURCE_URL = "https://gitlab.com/andrejr/csnake" 

21PYPI_URL = "https://pypi.org/project/csnake" 

22 

23 

24class DefStackEmptyError(IndexError): 

25 "Ended a if[n]def that wasn't started earlier. To ignore, use \ 

26 ignore_ifdef_stack=True as parameter" 

27 

28 

29class SwitchStackEmptyError(IndexError): 

30 "Ended a switch that wasn't started earlier. To ignore, use \ 

31 ignore_switch_stack=True as parameter" 

32 

33 

34class CodeWriter(CodeWriterLite): 

35 """Container class for C code, with methods to add C constructs. 

36 

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. 

40 

41 It also helps you to indent and dedent code, open or close code blocks 

42 consistently. 

43 

44 :class:`CodeWriter` is iterable (iterates over lines of code), and also has 

45 methods like :meth:`__contains__` and :meth:`__len__`. 

46 

47 Here's an overview of abc's :class:`CodeWriter` is a virtual subclass of: 

48 

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) 

56 

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. 

62 

63 Attributes: 

64 

65 lf(str): linefeed character used to separate lines of code when 

66 rendering code with :func:`str` or :attr:`code`. 

67 

68 lines(list(str)): lines of code. 

69 """ 

70 

71 __slots__ = ("_def_stack", "_switch_stack") 

72 

73 _CPP = "__cplusplus" 

74 

75 def __init__(self, lf: str = None, indent: Union[int, str] = 4) -> None: 

76 super().__init__(lf, indent) 

77 

78 # initialize values 

79 self._def_stack: List[str] = [] 

80 self._switch_stack: List[str] = [] 

81 

82 def add_autogen_comment(self, source_file_name: str = None) -> None: 

83 """Add a comment denoting the code was autogenerated by a script. 

84 

85 File name of the script the file is generated from is optional. 

86 

87 Args: 

88 source_file_name: name of the script the code was generated from 

89 

90 Examples: 

91 Without a script file name: 

92 

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 */ 

106 

107 With a script name: 

108 

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 ) 

135 

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) 

145 

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() 

151 

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. 

160 

161 The comment also adds the year to the copyright. 

162 

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) 

168 

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. 

172 

173 Raises: 

174 ValueError: if any of the arguments is of wrong type 

175 

176 Examples: 

177 Just license: 

178 

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 */ 

189 

190 With introduction: 

191 

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 */ 

204 

205 With authors (and year; year defaults to current year): 

206 

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() 

229 

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() 

237 

238 if not year: 

239 year = date.today().year 

240 else: 

241 year = int(year) 

242 

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() 

255 

256 if not isinstance(license_, str): 

257 raise TypeError("license_ must be a string.") 

258 

259 for line in license_.splitlines(): 

260 self.add_line(line) 

261 

262 self.end_comment() 

263 

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. 

271 

272 Macros may or may not have a value. 

273 

274 Args: 

275 name: macro name 

276 value: literal or variable assigned to the macro (optional) 

277 comment: comment accompanying macro definition 

278 

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] 

290 

291 Todo: 

292 

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 ) 

302 

303 self.add_line(line, comment=comment, ignore_indent=True) 

304 

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. 

310 

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`. 

314 

315 Args: 

316 define: name of the macro whose existence we're checking. 

317 

318 invert: (optional) whether this block is an :ccode:`#ifndef` 

319 (:code:`True`) or :ccode:`#ifdef` (:code:`False`, default). 

320 

321 comment: (optional) comment accompanying the statement. 

322 

323 Raises: 

324 ValueError: if one of the arguments is of the wrong type. 

325 

326 Examples: 

327 :meth:`start_if_def` and :meth:`end_if_def` in action, including 

328 nested ifdefs: 

329 

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) 

345 

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 ) 

355 

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. 

359 

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`. 

363 

364 Args: 

365 ignore_ifdef_stack: (optional) don't throw an exception 

366 :ccode:`#endif` if is unmatched. 

367 

368 Raises: 

369 ValueError: if one of the arguments is of the wrong type. 

370 

371 DefStackEmptyError: if there isn't a matching :code:`#ifdef` and 

372 :obj:`ignore_ifdef_stack` isn't set. 

373 

374 Examples: 

375 See :meth:`start_if_def`. 

376 """ 

377 

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 

385 

386 self.add_line("#endif", comment=def_name, ignore_indent=True) 

387 

388 def cpp_entry(self) -> None: 

389 """Start a conditional :ccode:`extern "C"` for use CPP compilers. 

390 

391 Examples: 

392 :meth:`cpp_entry` and :meth:`cpp_exit` in action: 

393 

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() 

411 

412 def cpp_exit(self) -> None: 

413 """End a conditional :ccode:`extern "C"` for use CPP compilers. 

414 

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() 

421 

422 def start_switch( 

423 self, switch: Union[CExtendedLiteral, VariableValue] 

424 ) -> None: 

425 """Start a switch statement. 

426 

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. 

430 

431 Switch statements can be nested, so :meth:`end_switch` closes the 

432 innermost switch. 

433 

434 Args: 

435 switch: literal or variable the choice depends on. 

436 

437 Examples: 

438 Demonstration of switch-related methods: 

439 

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() 

469 

470 def end_switch(self, ignore_switch_stack: bool = False) -> None: 

471 """End a switch statement. 

472 

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. 

476 

477 Switch statements can be nested, so :meth:`end_switch` closes the 

478 innermost switch. 

479 

480 Args: 

481 ignore_switch_stack: don't throw an exception if a switch start is 

482 missing 

483 

484 Raises: 

485 SwitchStackEmptyError: if :meth:`end_switch` is called outside of a 

486 switch statement, and :obj:`ignore_switch_stack` is :code:`False`. 

487 

488 Examples: 

489 See :meth:`start_switch`. 

490 """ 

491 self.close_brace() 

492 

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 

502 

503 self.add(f" /* ~switch ({switch_name}) */") 

504 

505 def add_switch_case( 

506 self, case=Union[CExtendedLiteral, VariableValue], comment: str = None 

507 ) -> None: 

508 """Add a switch case statement. 

509 

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. 

513 

514 Args: 

515 case: literal or variable representing the current case's value 

516 comment: accompanying inline comment 

517 

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() 

525 

526 def add_switch_default(self, comment: str = None) -> None: 

527 """Add a switch default statement. 

528 

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. 

532 

533 Switch statements can be nested, so :meth:`end_switch` closes the 

534 innermost switch. 

535 

536 Examples: 

537 See :meth:`start_switch`. 

538 """ 

539 self.add_line("default:", comment=comment) 

540 self.indent() 

541 

542 def add_switch_break(self) -> None: 

543 """Break a switch case. 

544 

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. 

548 

549 Examples: 

550 See :meth:`start_switch`. 

551 """ 

552 self.add_line("break;") 

553 self.dedent() 

554 

555 def add_switch_return( 

556 self, value: Union[CExtendedLiteral, VariableValue] = None 

557 ) -> None: 

558 """Return inside of a switch statement 

559 

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. 

563 

564 Args: 

565 value: literal or variable representing the value to return 

566 

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() 

576 

577 def include(self, name: str, comment: str = None) -> None: 

578 """Add an :ccode:`#include` directive. 

579 

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) 

583 

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. 

588 

589 comment: accompanying inline comment 

590 

591 Examples: 

592 All types of includes. 

593 

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}"' 

609 

610 self.add_line( 

611 "#include {name}".format(name=name), 

612 comment=comment, 

613 ignore_indent=True, 

614 ) 

615 

616 def add_enum(self, enum: Enum) -> None: 

617 """Add an enumeration definition. 

618 

619 Args: 

620 enum: enum in question 

621 

622 See Also: 

623 :class:`Enum` for details on the `enum` class 

624 

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 """ 

649 

650 if not isinstance(enum, Enum): 

651 raise TypeError('enum must be of type "Enum"') 

652 

653 self.add_lines(enum.generate_declaration(self._indent_unit).lines) 

654 

655 def add_variable_declaration( 

656 self, variable: Variable, extern: bool = False 

657 ) -> None: 

658 """Add a variable's declaration. 

659 

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) 

664 

665 See Also: 

666 :class:`Variable` for details on the `Variable` class 

667 

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 """ 

681 

682 if not isinstance(variable, Variable): 

683 raise TypeError("variable must be of type 'Variable'") 

684 

685 self.add_line( 

686 variable.generate_declaration(extern) + ";", 

687 comment=variable.comment, 

688 ) 

689 

690 def add_variable_initialization(self, variable: Variable) -> None: 

691 """Add a variable's initialization. 

692 

693 Args: 

694 variable: variable in question 

695 

696 See Also: 

697 :class:`Variable` for details on the `Variable` class 

698 

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 """ 

723 

724 if not isinstance(variable, Variable): 

725 raise TypeError("variable must be of type 'Variable'") 

726 

727 init_cwr = variable.generate_initialization(self._indent_unit) 

728 assert isinstance(init_cwr, Iterable) 

729 self.add_lines(init_cwr) 

730 

731 def add_struct(self, struct: Struct) -> None: 

732 """Add a struct declaration. 

733 

734 Args: 

735 struct: struct in question 

736 

737 See Also: 

738 :class:`Struct` for details on the `Struct` class. 

739 

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 """ 

760 

761 if not isinstance(struct, Struct): 

762 raise TypeError("struct must be of type 'Struct'") 

763 

764 declaration = struct.generate_declaration(indent=self._indent_unit) 

765 assert isinstance(declaration, Iterable) # mypy 

766 self.add_lines(declaration) 

767 

768 def add_function_prototype( 

769 self, func: Function, extern: bool = False, comment: str = None 

770 ) -> None: 

771 """Add a functions's prototype. 

772 

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 

778 

779 See Also: 

780 :class:`Function` for details on the `Function` class 

781 

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 """ 

797 

798 if not isinstance(func, Function): 

799 raise TypeError("func must be of type 'Function'") 

800 

801 self.add_line(func.generate_prototype(extern) + ";", comment=comment) 

802 

803 def add_function_definition(self, func: Function) -> None: 

804 """Add a functions's definition / implementation. 

805 

806 Args: 

807 func: function in question 

808 

809 See Also: 

810 :class:`Function` for details on the `Function` class 

811 

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'") 

833 

834 definition = func.generate_definition(self._indent_unit) 

835 assert isinstance(definition, Iterable) # mypy 

836 self.add_lines(definition) 

837 

838 def add_function_call(self, func: Function, *arg) -> None: 

839 """Add a call to a function with listed arguments. 

840 

841 Args: 

842 func: function in question 

843 \\*arg: (rest of the args) function's args in sequence 

844 

845 See Also: 

846 :class:`Function` for details on the `Function` class 

847 

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 """ 

863 

864 if not isinstance(func, Function): 

865 raise TypeError("func must be of type 'Function'") 

866 

867 self.add_line(func.generate_call(*arg) + ";") 

868 

869 def write_to_file(self, filename: str) -> None: 

870 """Write code to filename. 

871 

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)