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 pathlib import Path 

5from typing import Iterable 

6from typing import List 

7from typing import Mapping 

8from typing import Optional 

9from typing import Union 

10 

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 

21 

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

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

24 

25 

26class DefStackEmptyError(IndexError): 

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

28 ignore_ifdef_stack=True as parameter" 

29 

30 

31class SwitchStackEmptyError(IndexError): 

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

33 ignore_switch_stack=True as parameter" 

34 

35 

36class CodeWriter(CodeWriterLite): 

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

38 

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. 

42 

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

44 consistently. 

45 

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

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

48 

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

50 

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) 

58 

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. 

64 

65 Attributes: 

66 

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

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

69 

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

71 """ 

72 

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

74 

75 _CPP = "__cplusplus" 

76 

77 def __init__( 

78 self, lf: Optional[str] = None, indent: Union[int, str] = 4 

79 ) -> None: 

80 super().__init__(lf, indent) 

81 

82 # initialize values 

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

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

85 

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. 

90 

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

92 

93 Args: 

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

95 

96 Examples: 

97 Without a script file name: 

98 

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

112 

113 With a script name: 

114 

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 ) 

141 

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) 

151 

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

157 

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. 

166 

167 The comment also adds the year to the copyright. 

168 

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) 

174 

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. 

178 

179 Raises: 

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

181 

182 Examples: 

183 Just license: 

184 

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

195 

196 With introduction: 

197 

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

210 

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

212 

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

235 

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

243 

244 year = int(year or date.today().year) 

245 

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

258 

259 if not isinstance(license_, str): 

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

261 

262 for line in license_.splitlines(): 

263 self.add_line(line) 

264 

265 self.end_comment() 

266 

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. 

274 

275 Macros may or may not have a value. 

276 

277 Args: 

278 name: macro name 

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

280 comment: comment accompanying macro definition 

281 

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] 

293 

294 Todo: 

295 

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 ) 

307 

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

309 

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. 

315 

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

319 

320 Args: 

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

322 

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

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

325 

326 comment: (optional) comment accompanying the statement. 

327 

328 Raises: 

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

330 

331 Examples: 

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

333 nested ifdefs: 

334 

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) 

350 

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 ) 

360 

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. 

364 

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

368 

369 Args: 

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

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

372 

373 Raises: 

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

375 

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

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

378 

379 Examples: 

380 See :meth:`start_if_def`. 

381 """ 

382 

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 

390 

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

392 

393 def cpp_entry(self) -> None: 

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

395 

396 Examples: 

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

398 

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

416 

417 def cpp_exit(self) -> None: 

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

419 

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

426 

427 def start_switch( 

428 self, switch: Union[CExtendedLiteral, VariableValue] 

429 ) -> None: 

430 """Start a switch statement. 

431 

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. 

435 

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

437 innermost switch. 

438 

439 Args: 

440 switch: literal or variable the choice depends on. 

441 

442 Examples: 

443 Demonstration of switch-related methods: 

444 

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

474 

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

476 """End a switch statement. 

477 

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. 

481 

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

483 innermost switch. 

484 

485 Args: 

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

487 missing 

488 

489 Raises: 

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

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

492 

493 Examples: 

494 See :meth:`start_switch`. 

495 """ 

496 self.close_brace() 

497 

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 

505 

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

507 

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. 

514 

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. 

518 

519 Args: 

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

521 comment: accompanying inline comment 

522 

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

530 

531 def add_switch_default(self, comment: Optional[str] = None) -> None: 

532 """Add a switch default statement. 

533 

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. 

537 

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

539 innermost switch. 

540 

541 Examples: 

542 See :meth:`start_switch`. 

543 """ 

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

545 self.indent() 

546 

547 def add_switch_break(self) -> None: 

548 """Break a switch case. 

549 

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. 

553 

554 Examples: 

555 See :meth:`start_switch`. 

556 """ 

557 self.add_line("break;") 

558 self.dedent() 

559 

560 def add_switch_return( 

561 self, value: Optional[Union[CExtendedLiteral, VariableValue]] = None 

562 ) -> None: 

563 """Return inside of a switch statement 

564 

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. 

568 

569 Args: 

570 value: literal or variable representing the value to return 

571 

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

581 

582 def include(self, name: str, comment: Optional[str] = None) -> None: 

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

584 

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) 

588 

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. 

593 

594 comment: accompanying inline comment 

595 

596 Examples: 

597 All types of includes. 

598 

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

614 

615 self.add_line( 

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

617 comment=comment, 

618 ignore_indent=True, 

619 ) 

620 

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

622 """Add an enumeration definition. 

623 

624 Args: 

625 enum: enum in question 

626 

627 See Also: 

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

629 

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

654 

655 if not isinstance(enum, Enum): 

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

657 

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

659 

660 def add_variable_declaration( 

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

662 ) -> None: 

663 """Add a variable's declaration. 

664 

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) 

669 

670 See Also: 

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

672 

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

686 

687 if not isinstance(variable, Variable): 

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

689 

690 self.add_line( 

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

692 comment=variable.comment, 

693 ) 

694 

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

696 """Add a variable's initialization. 

697 

698 Args: 

699 variable: variable in question 

700 

701 See Also: 

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

703 

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

728 

729 if not isinstance(variable, Variable): 

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

731 

732 init_cwr = variable.generate_initialization(self._indent_unit) 

733 assert isinstance(init_cwr, Iterable) 

734 self.add_lines(init_cwr) 

735 

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

737 """Add a struct declaration. 

738 

739 Args: 

740 struct: struct in question 

741 

742 See Also: 

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

744 

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

765 

766 if not isinstance(struct, Struct): 

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

768 

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

770 assert isinstance(declaration, Iterable) # mypy 

771 self.add_lines(declaration) 

772 

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. 

780 

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 

786 

787 See Also: 

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

789 

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

805 

806 if not isinstance(func, Function): 

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

808 

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

810 

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

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

813 

814 Args: 

815 func: function in question 

816 

817 See Also: 

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

819 

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

841 

842 definition = func.generate_definition(self._indent_unit) 

843 assert isinstance(definition, Iterable) # mypy 

844 self.add_lines(definition) 

845 

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

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

848 

849 Args: 

850 func: function in question 

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

852 

853 See Also: 

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

855 

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

871 

872 if not isinstance(func, Function): 

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

874 

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

876 

877 def write_to_file(self, filename: Union[str, Path]) -> None: 

878 """Write code to filename. 

879 

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)