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

2""" 

3Classes that define C constructs. 

4""" 

5# TODO: make a static default for LiteralFormatters for using as default, instead 

6# of using another instance every time. 

7# TODO: add typedefs, especially for function pointers 

8import collections 

9from abc import ABCMeta 

10from abc import abstractmethod 

11from collections import deque 

12from collections import namedtuple 

13from itertools import chain 

14from typing import Callable 

15from typing import Collection 

16from typing import Iterable 

17from typing import Iterator 

18from typing import List 

19from typing import Mapping 

20from typing import Tuple 

21from typing import Union 

22 

23from .codewriterlite import CodeWriterLite 

24from .utils import assure_str 

25from .utils import seq_get 

26 

27 

28# internal types used by csnake as initialization values 

29class CIntLiteral(metaclass=ABCMeta): 

30 """ABC for all C integer literals.""" 

31 

32 

33CIntLiteral.register(int) 

34 

35 

36class CFloatLiteral(metaclass=ABCMeta): 

37 """ABC for all C floating point literals.""" 

38 

39 

40CFloatLiteral.register(float) 

41 

42 

43class CArrayLiteral(metaclass=ABCMeta): 

44 """ABC for array literals: sized Iterables except str, Mapping.""" 

45 

46 @classmethod 

47 def __subclasshook__(cls, subclass): 

48 if cls is CArrayLiteral: 

49 if ( 

50 issubclass(subclass, collections.abc.Sized) 

51 and issubclass(subclass, collections.abc.Iterable) 

52 and not issubclass(subclass, (str, collections.abc.Mapping)) 

53 ): 

54 return True 

55 return False 

56 return NotImplemented 

57 

58 

59CArrayLiteral.register(list) 

60CArrayLiteral.register(tuple) 

61 

62 

63class CStructLiteral(metaclass=ABCMeta): 

64 """ABC for struct literals: any Mapping (dict-like).""" 

65 

66 @classmethod 

67 def __subclasshook__(cls, subclass): 

68 if cls is CStructLiteral: 

69 return issubclass(subclass, collections.abc.Mapping) 

70 return NotImplemented 

71 

72 

73CStructLiteral.register(dict) 

74 

75 

76class CBasicLiteral(metaclass=ABCMeta): 

77 """ABC for literals of basic C types (int, float).""" 

78 

79 

80CBasicLiteral.register(CIntLiteral) 

81CBasicLiteral.register(CFloatLiteral) 

82 

83 

84class CExtendedLiteral(metaclass=ABCMeta): 

85 """ABC for basic C literals (int, float) + bool and char[].""" 

86 

87 

88CExtendedLiteral.register(CBasicLiteral) 

89CExtendedLiteral.register(bool) 

90CExtendedLiteral.register(str) 

91 

92 

93class CLiteral(metaclass=ABCMeta): 

94 """ABC for any C literal.""" 

95 

96 @classmethod 

97 def __subclasshook__(cls, subclass): 

98 if cls is CLiteral: 

99 return issubclass( 

100 subclass, (CExtendedLiteral, CStructLiteral, CArrayLiteral) 

101 ) 

102 return NotImplemented 

103 

104 

105CLiteral.register(CExtendedLiteral) 

106CLiteral.register(CStructLiteral) 

107 

108# conditional imports 

109try: 

110 import numpy as np 

111except ImportError: 

112 pass 

113else: 

114 CIntLiteral.register(np.integer) 

115 CFloatLiteral.register(np.floating) 

116 

117try: 

118 import sympy as sp 

119except ImportError: 

120 pass 

121else: 

122 CIntLiteral.register(sp.Integer) 

123 CFloatLiteral.register(sp.Float) 

124 

125 

126class ShapelessError(TypeError): 

127 """Argument doesn't have a shape.""" 

128 

129 

130def _shape(array: Iterable) -> tuple: 

131 """Return dimensions (shape) of a multidimensional list.""" 

132 

133 try: 

134 return array.shape # type: ignore 

135 except AttributeError: 

136 pass 

137 

138 if isinstance(array, str): 

139 raise ShapelessError("Strings don't have a shape.") 

140 

141 if isinstance(array, Mapping): 

142 raise ShapelessError("Mappings (like dicts) don't have a shape.") 

143 

144 curr = array 

145 shp: List[int] = [] 

146 

147 while True: 

148 if not isinstance(curr, CArrayLiteral): 

149 return tuple(shp) 

150 try: 

151 shp.append(len(curr)) 

152 itr = iter(curr) 

153 curr = next(itr) 

154 except (TypeError, IndexError): 

155 return tuple(shp) 

156 

157 

158# C constructs: 

159 

160 

161def simple_bool_formatter(value: bool) -> str: 

162 """Simple formatter that turns a bool into 'true' or 'false'. 

163 

164 Args: 

165 value: boolean to format 

166 

167 Returns: 

168 :obj:`str`'s 'true' or 'false' 

169 """ 

170 if value: 

171 return "true" 

172 else: 

173 return "false" 

174 

175 

176def simple_str_formatter(value: str) -> str: 

177 """Simple formatter that surrounds a str with quotes. 

178 

179 Args: 

180 value: string to format 

181 

182 Returns: 

183 :obj:`str` that's the input string surrounded with double quotes 

184 """ 

185 return f'"{value}"' 

186 

187 

188class LiteralFormatters: 

189 """Collection of formatters used for formatting literals. 

190 

191 Any values left out from constructor call is set to defaults. 

192 

193 Args: 

194 int_formatter: function to call when formatting :ccode:`int` literals 

195 float_formatter: function to call when formatting :ccode:`float` literals 

196 bool_formatter: function to call when formatting :ccode:`bool` literals 

197 string_formatter: function to call when formatting :ccode:`char[]` literals 

198 

199 Attributes: 

200 int_formatter: function to call when formatting :ccode:`int` literals 

201 float_formatter: function to call when formatting :ccode:`float` literals 

202 bool_formatter: function to call when formatting :ccode:`bool` literals 

203 string_formatter: function to call when formatting :ccode:`char[]` literals 

204 """ 

205 

206 __slots__ = ( 

207 "int_formatter", 

208 "float_formatter", 

209 "string_formatter", 

210 "bool_formatter", 

211 ) 

212 

213 def __init__( 

214 self, 

215 int_formatter: Callable = int, 

216 float_formatter: Callable = float, 

217 bool_formatter: Callable = simple_bool_formatter, 

218 string_formatter: Callable = simple_str_formatter, 

219 ) -> None: 

220 self.int_formatter = int_formatter 

221 self.float_formatter = float_formatter 

222 self.bool_formatter = bool_formatter 

223 self.string_formatter = string_formatter 

224 

225 @classmethod 

226 def empty(cls): 

227 """Returns an empty :class: instance (`None` for every parameter).""" 

228 return cls(**{key: None for key in cls.__slots__}) 

229 

230 def __repr__(self): 

231 """Simple printout of parameter values.""" 

232 lines = (f" {slot}: {getattr(self, slot)}" for slot in self.__slots__) 

233 return "formatters {\n" + "\n".join(lines) + "\n}" 

234 

235 def replace(self, *args, **kwargs): 

236 """Return a copy of collection with certain formatters replaced.""" 

237 

238 if len(args) == 1 and not kwargs: 

239 arg = next(iter(args)) 

240 if isinstance(arg, LiteralFormatters): 

241 

242 initializer = { 

243 slot: getattr(arg, slot) 

244 if getattr(arg, slot) is not None 

245 else getattr(self, slot) 

246 for slot in self.__slots__ 

247 } 

248 

249 return LiteralFormatters(**initializer) 

250 

251 if isinstance(arg, Mapping): 

252 return self.replace(**arg) 

253 

254 if kwargs and not args: 

255 

256 diff = set(kwargs.keys()) - set(self.__slots__) 

257 if diff: 

258 raise TypeError( 

259 f"Arguments {str(diff)} are invalid for this function" 

260 ) 

261 

262 initializer = { 

263 slot: kwargs[slot] if slot in kwargs else getattr(self, slot) 

264 for slot in self.__slots__ 

265 } 

266 

267 return LiteralFormatters(**initializer) 

268 

269 raise TypeError("Invalid arguments for this function") 

270 

271 @classmethod 

272 def partial(cls, dictionary: Mapping): 

273 """Create a partial formatter collection based on a dict.""" 

274 rtnval = cls.empty() 

275 return rtnval.replace(dictionary) 

276 

277 

278class FormattedLiteral: 

279 """A C literal with its formatter collection altered. 

280 

281 The accompanying literal formatter collection will be used when generating 

282 the initialization :obj:`str`. 

283 

284 Args: 

285 \\*\\*kwargs: keyword arguments for each of the formatters in 

286 ['int_formatter', 'float_formatter', 'bool_formatter', 

287 'string_formatter'] we want to change for this literal. Every 

288 missing formatter is inherited from the literal (or default). 

289 

290 Attributes: 

291 value: value of literal 

292 formatter: collection of literal formatters 

293 """ 

294 

295 __slots__ = ("value", "formatter") 

296 

297 def __init__(self, value: CLiteral, *args, **kwargs) -> None: 

298 self.value = value 

299 if args: 

300 raise ValueError(f"Unexpected arguments: {args}") 

301 

302 self.formatter = LiteralFormatters.partial(kwargs) 

303 

304 

305class VariableValue(metaclass=ABCMeta): 

306 """Abstract base class for any initializer value based on a variable. 

307 

308 Sometimes we want to initialize a value to another variable, but in some 

309 more complicated manner: using the address-of operator, dereference 

310 operator, subscripting, typecasting... This is an ABC for those 

311 modifiers. 

312 """ 

313 

314 @abstractmethod 

315 def init_target_code(self, formatters: LiteralFormatters = None): 

316 """Return code used for variable initialization, formatted with the 

317 supplied formatters. 

318 

319 Args: 

320 formatters: collection of formatters used for formatting the 

321 initialization :obj:`str` 

322 """ 

323 pass 

324 

325 @abstractmethod 

326 def __str__(self): 

327 """Return :obj:`str` containing initialization code.""" 

328 

329 

330def generate_c_value_initializer( 

331 cval: Union[CExtendedLiteral, VariableValue], 

332 formatters: LiteralFormatters = None, 

333) -> str: 

334 """Generate an initialization str from a literal using formatters.""" 

335 if formatters is None: 

336 formatters = LiteralFormatters() 

337 

338 if isinstance(cval, VariableValue): 

339 return str(cval.init_target_code(formatters)) 

340 if isinstance(cval, bool): 

341 return str(formatters.bool_formatter(cval)) 

342 if isinstance(cval, CIntLiteral): 

343 return str(formatters.int_formatter(cval)) 

344 if isinstance(cval, CFloatLiteral): 

345 return str(formatters.float_formatter(cval)) 

346 if isinstance(cval, str): 

347 return str(formatters.string_formatter(cval)) 

348 

349 raise TypeError("cval must be a CExtendedLiteral.") 

350 

351 

352class _ArrStructInitGen: 

353 """Classes and functions that help with initialization generation.""" 

354 

355 __slots__ = "type_action_pairs" 

356 

357 def __registration(self): 

358 "Fake function to register slots with mypy. Not meant to be called." 

359 self.type_action_pairs = None 

360 raise NotImplementedError 

361 

362 class OpenBrace: 

363 """Helper class to identify open braces while printing.""" 

364 

365 __slots__ = () 

366 

367 class ClosedBrace: 

368 """Helper class to identify closed braces while printing. 

369 

370 struct_closing means the brace closes a struct initialization 

371 """ 

372 

373 __slots__ = "struct_closing" 

374 

375 def __init__(self, struct_closing: bool = False) -> None: 

376 self.struct_closing = bool(struct_closing) 

377 

378 class Designator: 

379 """Helper class to identify struct designators.""" 

380 

381 __slots__ = ("name",) 

382 

383 def __init__(self, name: str) -> None: 

384 self.name = name 

385 

386 @staticmethod 

387 def lookthrough_ignoring_format( 

388 forvar: Union[FormattedLiteral, CLiteral] 

389 ) -> Union[CLiteral, FormattedLiteral]: 

390 if isinstance(forvar, FormattedLiteral): 

391 return _ArrStructInitGen.lookthrough_ignoring_format(forvar.value) 

392 return forvar 

393 

394 @staticmethod 

395 def lookahead_ignoring_format( 

396 stack: deque, 

397 ) -> Union[ 

398 Designator, 

399 OpenBrace, 

400 ClosedBrace, 

401 CArrayLiteral, 

402 CStructLiteral, 

403 CExtendedLiteral, 

404 VariableValue, 

405 ]: 

406 index = -1 

407 while True: 

408 try: 

409 lookahead = stack[index] 

410 except IndexError as e: 

411 raise IndexError( 

412 "no next element of interest on stack." 

413 ) from e 

414 

415 if isinstance(lookahead, FormattedLiteral): 

416 res = _ArrStructInitGen.lookthrough_ignoring_format(lookahead) 

417 assert isinstance(res, CLiteral) 

418 return res # type: ignore 

419 

420 if isinstance(lookahead, LiteralFormatters): 

421 index -= 1 

422 continue 

423 

424 return lookahead 

425 

426 @staticmethod 

427 def c_array_literal_handler( 

428 top, 

429 stack: deque, 

430 writer: CodeWriterLite, 

431 formatters: LiteralFormatters, 

432 ) -> LiteralFormatters: 

433 stack.append(_ArrStructInitGen.ClosedBrace()) 

434 try: 

435 stack.extend(reversed(top)) # type: ignore 

436 except TypeError: 

437 tempdeq: deque = deque() 

438 assert isinstance(top, Iterable) 

439 tempdeq.extendleft(top) 

440 stack.extend(tempdeq) 

441 stack.append(_ArrStructInitGen.OpenBrace()) 

442 return formatters 

443 

444 @staticmethod 

445 def c_struct_literal_handler( 

446 top, 

447 stack: deque, 

448 writer: CodeWriterLite, 

449 formatters: LiteralFormatters, 

450 ) -> LiteralFormatters: 

451 stack.append(_ArrStructInitGen.ClosedBrace(struct_closing=True)) 

452 assert isinstance(top, Mapping) 

453 dict_pairs = ( 

454 (value, _ArrStructInitGen.Designator(key)) 

455 for key, value in reversed(list(top.items())) 

456 ) 

457 flatdict = (item for sublist in dict_pairs for item in sublist) 

458 stack.extend(flatdict) 

459 stack.append(_ArrStructInitGen.OpenBrace()) 

460 return formatters 

461 

462 @staticmethod 

463 def closed_brace_handler( 

464 top, 

465 stack: deque, 

466 writer: CodeWriterLite, 

467 formatters: LiteralFormatters, 

468 ) -> LiteralFormatters: 

469 writer.dedent() 

470 if top.struct_closing: 

471 writer.add_line("}") 

472 else: 

473 writer.add("}") 

474 

475 try: 

476 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack) 

477 except IndexError: 

478 pass 

479 else: 

480 if isinstance(lookahead, _ArrStructInitGen.ClosedBrace): 

481 writer.dedent() 

482 writer.add_line("") 

483 writer.indent() 

484 elif isinstance( 

485 lookahead, (CArrayLiteral, _ArrStructInitGen.OpenBrace) 

486 ): 

487 writer.add(",") 

488 writer.add_line("") 

489 else: 

490 writer.add(",") 

491 return formatters 

492 

493 @staticmethod 

494 def open_brace_handler( 

495 top, 

496 stack: deque, 

497 writer: CodeWriterLite, 

498 formatters: LiteralFormatters, 

499 ) -> LiteralFormatters: 

500 

501 writer.add("{") 

502 writer.indent() 

503 

504 try: 

505 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack) 

506 except IndexError: 

507 pass 

508 else: 

509 if isinstance( 

510 lookahead, 

511 (_ArrStructInitGen.OpenBrace, CArrayLiteral, CStructLiteral), 

512 ): 

513 writer.add_line("") 

514 return formatters 

515 

516 @staticmethod 

517 def literal_or_value_handler( 

518 top, 

519 stack: deque, 

520 writer: CodeWriterLite, 

521 formatters: LiteralFormatters, 

522 ) -> LiteralFormatters: 

523 writer.add(generate_c_value_initializer(top, formatters)) 

524 

525 try: 

526 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack) 

527 except IndexError: 

528 pass 

529 else: 

530 if isinstance( 

531 lookahead, 

532 ( 

533 CExtendedLiteral, 

534 VariableValue, 

535 _ArrStructInitGen.Designator, 

536 ), 

537 ): 

538 writer.add(",") 

539 if isinstance(lookahead, (CExtendedLiteral, VariableValue)): 

540 writer.add(" ") 

541 return formatters 

542 

543 @staticmethod 

544 def designator_handler( 

545 top, 

546 stack: deque, 

547 writer: CodeWriterLite, 

548 formatters: LiteralFormatters, 

549 ) -> LiteralFormatters: 

550 writer.add_line("." + top.name + " = ") 

551 return formatters 

552 

553 @staticmethod 

554 def formatted_literal_handler( 

555 top, 

556 stack: deque, 

557 writer: CodeWriterLite, 

558 formatters: LiteralFormatters, 

559 ) -> LiteralFormatters: 

560 new_formatters = formatters.replace(top.formatter) 

561 

562 stack.append(formatters) 

563 stack.append(top.value) 

564 stack.append(new_formatters) 

565 return formatters 

566 

567 @staticmethod 

568 def literal_formatters_handler( 

569 top, 

570 stack: deque, 

571 writer: CodeWriterLite, 

572 formatters: LiteralFormatters, 

573 ) -> LiteralFormatters: 

574 formatters = top 

575 return formatters 

576 

577 TypeActionPair = namedtuple("TypeActionPair", ("types", "action")) 

578 

579 

580_ArrStructInitGen.type_action_pairs = ( 

581 _ArrStructInitGen.TypeActionPair( 

582 types=(CArrayLiteral), action=_ArrStructInitGen.c_array_literal_handler 

583 ), 

584 _ArrStructInitGen.TypeActionPair( 

585 types=(CStructLiteral), 

586 action=_ArrStructInitGen.c_struct_literal_handler, 

587 ), 

588 _ArrStructInitGen.TypeActionPair( 

589 types=(_ArrStructInitGen.ClosedBrace), 

590 action=_ArrStructInitGen.closed_brace_handler, 

591 ), 

592 _ArrStructInitGen.TypeActionPair( 

593 types=(_ArrStructInitGen.OpenBrace), 

594 action=_ArrStructInitGen.open_brace_handler, 

595 ), 

596 _ArrStructInitGen.TypeActionPair( 

597 types=(CExtendedLiteral, VariableValue), 

598 action=_ArrStructInitGen.literal_or_value_handler, 

599 ), 

600 _ArrStructInitGen.TypeActionPair( 

601 types=(_ArrStructInitGen.Designator), 

602 action=_ArrStructInitGen.designator_handler, 

603 ), 

604 _ArrStructInitGen.TypeActionPair( 

605 types=(FormattedLiteral), 

606 action=_ArrStructInitGen.formatted_literal_handler, 

607 ), 

608 _ArrStructInitGen.TypeActionPair( 

609 types=(LiteralFormatters), 

610 action=_ArrStructInitGen.literal_formatters_handler, 

611 ), 

612) 

613 

614 

615class NoValueError(ValueError): 

616 """Variable has no value and hence cannot be initialized.""" 

617 

618 

619class FuncPtr: 

620 """Class describing function pointer args and return type. 

621 

622 This class made for declaring function pointer type specifics for use with 

623 :class:`Variable`\\ s as their `type` argument. 

624 

625 Args: 

626 return_type: :obj:`str` containing the return type 

627 arguments: an iterable which yields one the following types: 

628 

629 * a :class:`Variable` 

630 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings 

631 (`name`, `primitive`) 

632 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`, 

633 `primitive`) 

634 """ 

635 

636 __slots__ = ("return_type", "arguments") 

637 

638 # TODO unify the creation to be the same as Function 

639 

640 def __init__( 

641 self, return_type: str, arguments: Iterable = None, comment: str = None 

642 ) -> None: 

643 

644 self.return_type = assure_str(return_type) 

645 self.arguments: List[Variable] = [] 

646 

647 if arguments is not None: 

648 for arg in arguments: 

649 self.arguments.append(_get_variable(arg)) 

650 

651 def get_declaration( 

652 self, name: str, qualifiers: str = None, array: str = None 

653 ) -> str: 

654 """Generate the whole declaration :obj:`str` according to parameters. 

655 

656 This method is meant to be called from 

657 :meth:`Variable.generate_declaration` and 

658 :meth:`Variable.generate_initialization` function and is probably 

659 useless to You elsewhere. 

660 """ 

661 jointargs = ", ".join( 

662 arg.generate_declaration() for arg in self.arguments 

663 ) 

664 

665 retval = "{rt} (*{qual}{name}{arr})({arguments})".format( 

666 rt=self.return_type, 

667 qual=qualifiers if qualifiers else "", 

668 name=name, 

669 arguments=jointargs if self.arguments else "", 

670 arr=array if array else "", 

671 ) 

672 

673 return retval 

674 

675 

676class Variable: 

677 """Class describing C variable contruct. 

678 

679 You can generate declarations and initializations for variables, and 

680 variables can be initialized to very complex values (you can init an array 

681 variable to an array of :ccode:`struct` containing arrays that contain 

682 :ccode:`structs`, ...). 

683 

684 The formatting of initialization :obj:`str`\\ s can be altered by encasing 

685 some level of the initialization with a :class:`FormattedLiteral`. 

686 

687 As for function values, the following (Python input → C code) translations 

688 are assumed (`numpy` and `scipy` `int`\\ s and `float`\\ s are considered ints 

689 and floats): 

690 

691 - :obj:`dict` → :ccode:`struct` literal 

692 - :obj:`list` or `tuple` → array literal 

693 - :obj:`int` → :ccode:`int` literal or :ccode:`int` literal 

694 - :obj:`float` → :ccode:`float` literal or :ccode:`double` literal 

695 - :obj:`bool` → :ccode:`bool` literal or :ccode:`int` literal 

696 - :class:`Variable` → name of the variable 

697 

698 There is no type checking for initializations. That's what your compiler is 

699 for, no sense in replicating such functionality here. 

700 

701 Args: 

702 name: name of the variable 

703 

704 primitive: :obj:`str` name or :class:`FuncPtr` defining the variable's 

705 type 

706 

707 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the 

708 variable's qualifiers (:ccode:`const`, :ccode:`volatile`, ...) 

709 array: :class:`Sequence` of :obj:`int` defining the dimensions of a 

710 (possibly multidimensional) array. If it's left out, it's inferred 

711 from `value`. 

712 

713 comment: accompanying comment 

714 value: variable's value, used for initialization. Explained in detail 

715 above 

716 

717 """ 

718 

719 __slots__ = ( 

720 "name", 

721 "primitive", 

722 "qualifiers", 

723 "array", 

724 "comment", 

725 "value", 

726 ) 

727 

728 def __init__( 

729 self, 

730 name: str, 

731 primitive: Union[str, FuncPtr], 

732 qualifiers: Union[Iterable[str], str] = None, 

733 array: Iterable = None, 

734 comment: str = None, 

735 value: CLiteral = None, 

736 ) -> None: 

737 

738 self.name = assure_str(name) 

739 if isinstance(primitive, FuncPtr): 

740 self.primitive: Union[FuncPtr, str] = primitive 

741 else: 

742 self.primitive = assure_str(primitive) 

743 self.comment = assure_str(comment) if comment is not None else None 

744 self.array = array 

745 self.qualifiers = qualifiers 

746 self.value = value 

747 

748 @staticmethod 

749 def merge_formatters( 

750 value: CLiteral, formatters: LiteralFormatters = None 

751 ) -> Tuple[CLiteral, LiteralFormatters]: 

752 if not formatters: 

753 formatters = LiteralFormatters() 

754 

755 while isinstance(value, FormattedLiteral): 

756 formatters = formatters.replace(value.formatter) 

757 value = value.value 

758 

759 return (value, formatters) 

760 

761 def __array_dimensions(self) -> str: 

762 value = self.value 

763 if value is not None: 

764 value, _ = self.merge_formatters(value) 

765 

766 if isinstance(self.array, CArrayLiteral): 

767 array = "".join(f"[{dim}]" for dim in self.array) 

768 elif self.array is not None: 

769 array = f"[{self.array}]" 

770 elif self.array is None and isinstance(value, str): 

771 array = "[]" 

772 elif self.array is None and isinstance(value, Iterable): 

773 try: 

774 val_shape = _shape(value) 

775 array = "".join(f"[{dim}]" for dim in val_shape) 

776 except ShapelessError: 

777 array = "" 

778 else: 

779 array = "" 

780 

781 return array 

782 

783 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

784 """Return a :obj:`str` used for initialization of other 

785 :obj:`Variable`\\ s""" 

786 return self.name 

787 

788 def generate_declaration(self, extern: bool = False) -> str: 

789 """Return a declaration :obj:`str`. 

790 

791 Doesn't end with a semicolon (;). 

792 

793 Args: 

794 extern: whether or not the value is to be declared as 

795 :ccode:`extern` 

796 

797 Examples: 

798 >>> import numpy as np 

799 >>> from csnake import Variable 

800 >>> var = Variable( 

801 ... "test", 

802 ... primitive="int", 

803 ... value=np.arange(24).reshape((2, 3, 4)) 

804 ... ) 

805 >>> print(var.generate_declaration()) 

806 int test[2][3][4] 

807 """ 

808 

809 if self.qualifiers is not None: 

810 if isinstance(self.qualifiers, str): 

811 qual = self.qualifiers + " " 

812 else: 

813 qual = " ".join(self.qualifiers) + " " 

814 else: 

815 qual = "" 

816 

817 array = self.__array_dimensions() 

818 

819 if isinstance(self.primitive, FuncPtr): 

820 decl = self.primitive.get_declaration( 

821 name=self.name, qualifiers=qual, array=array 

822 ) 

823 

824 return "{ext}{decl}".format( 

825 ext="extern " if extern else "", decl=decl 

826 ) 

827 

828 return "{ext}{qual}{prim} {name}{array}".format( 

829 ext="extern " if extern else "", 

830 qual=qual, 

831 prim=self.primitive, 

832 name=self.name, 

833 array=array, 

834 ) 

835 

836 @property 

837 def declaration(self) -> str: 

838 """Declaration string. 

839 

840 Ends with a semicolon (;). 

841 

842 See Also: 

843 :meth:`generate_declaration` for the underlying method. 

844 """ 

845 return self.generate_declaration(extern=False) + ";" 

846 

847 def __generate_array_struct_initialization( 

848 self, 

849 array: Union[CArrayLiteral, CStructLiteral], 

850 indent: Union[int, str] = 4, 

851 writer: CodeWriterLite = None, 

852 formatters: LiteralFormatters = None, 

853 ) -> None: 

854 """Print (multi)dimensional arrays.""" 

855 

856 if self.value is None: 

857 raise NoValueError 

858 

859 stack: deque = deque() 

860 stack.append(array) 

861 if not formatters: 

862 formatters = LiteralFormatters() 

863 if writer is None: 

864 writer = CodeWriterLite(indent=indent) 

865 

866 while stack: 

867 top = stack.pop() 

868 for types, action in _ArrStructInitGen.type_action_pairs: 

869 if isinstance(top, types): 

870 formatters = action( 

871 top=top, 

872 stack=stack, 

873 writer=writer, 

874 formatters=formatters, 

875 ) 

876 break 

877 else: 

878 raise TypeError("Unknown type on stack") 

879 

880 def generate_initialization( 

881 self, indent: Union[int, str] = 4 

882 ) -> CodeWriterLite: 

883 """Return a :class:`CodeWriterLite` instance containing the 

884 initialization code for this :class:`Variable`. 

885 

886 Ends with a semicolon (;). 

887 

888 Args: 

889 indent: indent :obj:`str` or :obj:`int` denoting the number of 

890 spaces for indentation 

891 

892 

893 Example: 

894 >>> import numpy as np 

895 >>> from csnake import Variable 

896 >>> var = Variable( 

897 ... "test", 

898 ... primitive="int", 

899 ... value=np.arange(24).reshape((2, 3, 4)) 

900 ... ) 

901 >>> print(var.generate_initialization()) 

902 int test[2][3][4] = { 

903 { 

904 {0, 1, 2, 3}, 

905 {4, 5, 6, 7}, 

906 {8, 9, 10, 11} 

907 }, 

908 { 

909 {12, 13, 14, 15}, 

910 {16, 17, 18, 19}, 

911 {20, 21, 22, 23} 

912 } 

913 }; 

914 

915 """ 

916 

917 # main part: generating initializer 

918 if self.value is None: 

919 raise NoValueError 

920 

921 writer = CodeWriterLite(indent=indent) 

922 

923 if not isinstance(self.qualifiers, str) and isinstance( 

924 self.qualifiers, Iterable 

925 ): 

926 qual = " ".join(self.qualifiers) + " " 

927 elif self.qualifiers is not None: 

928 qual = assure_str(self.qualifiers) + " " 

929 else: 

930 qual = "" 

931 

932 array = self.__array_dimensions() 

933 

934 if isinstance(self.primitive, FuncPtr): 

935 decl = self.primitive.get_declaration( 

936 name=self.name, qualifiers=qual, array=array 

937 ) 

938 writer.add_line(decl) 

939 else: 

940 writer.add_line(f"{qual}{self.primitive} {self.name}{array}") 

941 

942 writer.add(" = ") 

943 

944 value, formatters = self.merge_formatters(self.value) 

945 

946 if isinstance(value, (CArrayLiteral, CStructLiteral)): 

947 self.__generate_array_struct_initialization( 

948 value, indent, writer, formatters 

949 ) 

950 else: 

951 formatters = LiteralFormatters() 

952 assignment = generate_c_value_initializer(value, formatters) 

953 writer.add(assignment) 

954 

955 writer.add(";") 

956 return writer 

957 

958 @property 

959 def initialization(self) -> str: 

960 """Initialization :obj:`str`. 

961 

962 Ends with a semicolon (;). 

963 

964 See Also: 

965 :meth:`generate_initialization` for the underlying method. 

966 """ 

967 return str(self.generate_initialization()) 

968 

969 def __str__(self): 

970 """Initialization (if value in not None) or declaration :obj:`str`. 

971 

972 Falls back to declaration if :attr:`value` is None. 

973 

974 Ends with a semicolon (;). 

975 

976 See Also: 

977 :meth:`generate_initialization` for the initialization method. 

978 :meth:`generate_declaration` for the declaration method. 

979 """ 

980 try: 

981 return self.initialization 

982 except NoValueError: 

983 return self.declaration 

984 

985 

986# no VariableValue is also a VariableValue! 

987VariableValue.register(Variable) 

988 

989 

990def _get_variable(variable: Union[Variable, Collection, Mapping]) -> Variable: 

991 """Get a Variable out of one of the following: 

992 

993 * a Variable (idempotent) 

994 

995 * a Collection (tuple/list-like) of 2 strings (name, primitive) 

996 

997 * a Mapping (dict-like) with keys (name, primitive) 

998 """ 

999 if isinstance(variable, Variable): 

1000 return variable 

1001 elif isinstance(variable, Mapping): 

1002 var = Variable(**variable) 

1003 return var 

1004 elif isinstance(variable, Collection): 

1005 if len(variable) != 2: 

1006 raise TypeError( 

1007 "variable must be a Collection with len(variable) == 2" 

1008 ) 

1009 var = Variable(*variable) 

1010 return var 

1011 else: 

1012 raise TypeError( 

1013 "variable must be one of (Variable, Collection, Mapping)" 

1014 ) 

1015 

1016 

1017class Struct: 

1018 """Class describing C :ccode:`struct` construct. 

1019 

1020 Args: 

1021 name: name of struct 

1022 typedef: whether or not the struct is :ccode:`typedef`'d 

1023 

1024 Attributes: 

1025 name: name of struct 

1026 typedef: whether or not the struct is :ccode:`typedef`'d 

1027 variables: :obj:`list` of :ccode:`struct`'s variables 

1028 """ 

1029 

1030 __slots__ = ("name", "variables", "typedef") 

1031 

1032 def __init__(self, name: str, typedef: bool = False) -> None: 

1033 self.name = assure_str(name) 

1034 self.variables: List[Variable] = [] 

1035 self.typedef = bool(typedef) 

1036 

1037 def add_variable(self, variable: Union[Variable, Collection, Mapping]): 

1038 """Add a variable to `struct`. 

1039 

1040 Variables inside of a :class:`Struct` are ordered (added sequentially). 

1041 

1042 Args: 

1043 variable: variable to add. It can be defined in multiple ways. 

1044 

1045 `variable` can be: 

1046 

1047 * a :class:`Variable` 

1048 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 

1049 :obj:`str`\\ s (`name`, `primitive`) 

1050 * a :class:`Mapping` (:obj:`dict`-like) with keys ['name', 

1051 'primitive'] 

1052 """ 

1053 

1054 proc_var = _get_variable(variable) 

1055 self.variables.append(proc_var) 

1056 

1057 def generate_declaration( 

1058 self, indent: Union[int, str] = 4 

1059 ) -> CodeWriterLite: 

1060 """Generate a :class:`CodeWriterLite` instance containing the 

1061 initialization code for this :class:`Struct`. 

1062 

1063 Args: 

1064 indent: indent :obj:`str` or :obj:`int` denoting the number of 

1065 spaces for indentation 

1066 

1067 Example: 

1068 >>> from csnake import Variable, Struct 

1069 >>> strct = Struct("strname", typedef=False) 

1070 >>> var1 = Variable("var1", "int") 

1071 >>> var2 = Variable("var2", "int", value=range(10)) 

1072 >>> strct.add_variable(var1) 

1073 >>> strct.add_variable(var2) 

1074 >>> strct.add_variable(("var3", "int")) 

1075 >>> strct.add_variable({"name": "var4", "primitive": "int"}) 

1076 >>> print(strct.generate_declaration()) 

1077 struct strname 

1078 { 

1079 int var1; 

1080 int var2[10]; 

1081 int var3; 

1082 int var4; 

1083 }; 

1084 """ 

1085 writer = CodeWriterLite(indent=indent) 

1086 

1087 if self.typedef: 

1088 writer.add_line("typedef struct") 

1089 else: 

1090 writer.add_line(f"struct {self.name}") 

1091 

1092 writer.open_brace() 

1093 for var in self.variables: 

1094 writer.add_line(var.declaration) 

1095 writer.close_brace() 

1096 

1097 if self.typedef: 

1098 writer.add(" " + self.name + ";") 

1099 else: 

1100 writer.add(";") 

1101 

1102 return writer 

1103 

1104 @property 

1105 def declaration(self): 

1106 """:class:`CodeWriterLite` instance containing the 

1107 declaration code for this :class:`Struct`. 

1108 

1109 See Also: 

1110 :meth:`generate_declaration` for the underlying method. 

1111 """ 

1112 

1113 return self.generate_declaration() 

1114 

1115 def __str__(self): 

1116 """Generate a :obj:`str` instance containing the 

1117 declaration code for this :class:`Struct`.""" 

1118 return str(self.generate_declaration()) 

1119 

1120 

1121class AddressOf(VariableValue): 

1122 """Address of (&) VariableValue for variable initialization. 

1123 

1124 Subclass of :class:`VariableValue` that returns an initialization string 

1125 containing the & (address of) used on a value. 

1126 

1127 Args: 

1128 variable: variable to return the address of. 

1129 

1130 Attributes: 

1131 variable: variable to return the address of. 

1132 

1133 Examples: 

1134 >>> from csnake import Variable, AddressOf 

1135 >>> var1 = Variable("var1", "int") 

1136 >>> addrof_var1 = AddressOf(var1) 

1137 >>> var2 = Variable("var2", "int", value=addrof_var1) 

1138 >>> print(var2) 

1139 int var2 = &var1; 

1140 """ 

1141 

1142 def __init__(self, variable: Union[VariableValue]) -> None: 

1143 if not isinstance(variable, (VariableValue)): 

1144 raise TypeError("variable must be of type VariableValue.") 

1145 self.variable = variable 

1146 

1147 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1148 """Return a :obj:`str` used for initialization of other 

1149 :obj:`Variable`\\ s""" 

1150 return f"&{generate_c_value_initializer(self.variable, formatters)}" 

1151 

1152 def __str__(self): 

1153 """Initialization string.""" 

1154 return self.init_target_code() 

1155 

1156 

1157class Dereference(VariableValue): 

1158 """Dereference (*) modifier for variable initialization. 

1159 

1160 Subclass of :class:`VariableValue` that returns an initialization string 

1161 containing the * (dereference) used on a value. 

1162 

1163 Args: 

1164 value: value to dereference. 

1165 

1166 Attributes: 

1167 value: value to dereference. 

1168 

1169 Examples: 

1170 >>> from csnake import Variable, Dereference 

1171 >>> var1 = Variable("var1", "int") 

1172 >>> derefd_var1 = Dereference(var1) 

1173 >>> var2 = Variable("var2", "int", value=derefd_var1) 

1174 >>> print(var2) 

1175 int var2 = *var1; 

1176 >>> derefd_number = Dereference(16) 

1177 >>> var3 = Variable("var3", "int", value=derefd_number) 

1178 >>> print(var3) 

1179 int var3 = *16; 

1180 """ 

1181 

1182 __slots__ = "value" 

1183 

1184 def __init__(self, value: Union[CExtendedLiteral, VariableValue]) -> None: 

1185 if not isinstance(value, (CExtendedLiteral, VariableValue)): 

1186 raise TypeError("value must be of type VariableValue or CLiteral.") 

1187 self.value = value 

1188 

1189 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1190 """Return a :obj:`str` used for initialization of other 

1191 :obj:`value`\\ s""" 

1192 return f"*{generate_c_value_initializer(self.value, formatters)}" 

1193 

1194 def __str__(self): 

1195 """Initialization string.""" 

1196 return self.init_target_code() 

1197 

1198 

1199class Typecast(VariableValue): 

1200 """Typecast modifier for variable initialization. 

1201 

1202 Subclass of :class:`VariableValue` that returns an initialization string 

1203 containing the typecast to `type` used on a value. 

1204 

1205 Args: 

1206 value: value to be typecast 

1207 cast: type to cast to 

1208 

1209 Attributes: 

1210 value: value to be typecast 

1211 cast: type to cast to 

1212 

1213 Examples: 

1214 >>> from csnake import Variable, Typecast 

1215 >>> var1 = Variable("var1", "int") 

1216 >>> cast_var1 = Typecast(var1, 'long') 

1217 >>> var2 = Variable("var2", "int", value=cast_var1) 

1218 >>> print(var2) 

1219 int var2 = (long) var1; 

1220 >>> cast_number = Typecast(16, 'long') 

1221 >>> var3 = Variable("var3", "int", value=cast_number) 

1222 >>> print(var3) 

1223 int var3 = (long) 16; 

1224 """ 

1225 

1226 __slots__ = ("subscript", "cast") 

1227 

1228 def __init__( 

1229 self, value: Union[CExtendedLiteral, VariableValue], cast: str 

1230 ) -> None: 

1231 if not isinstance(value, (CExtendedLiteral, VariableValue)): 

1232 raise TypeError( 

1233 "variable must be of type VariableValue or CLiteral." 

1234 ) 

1235 self.value = value 

1236 self.cast = assure_str(cast) 

1237 

1238 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1239 """Return a :obj:`str` used for initialization of other 

1240 :obj:`value`\\ s""" 

1241 initializer = generate_c_value_initializer(self.value, formatters) 

1242 return f"({self.cast}) {initializer}" 

1243 

1244 def __str__(self): 

1245 """Initialization string.""" 

1246 return self.init_target_code 

1247 

1248 

1249class Subscript(VariableValue): 

1250 """Subscript ([]) modifier for variable initialization. 

1251 

1252 Subclass of :class:`VariableValue` that returns an initialization string 

1253 with a subscripted value. 

1254 

1255 Args: 

1256 variable: variable to be typecast 

1257 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or 

1258 `tuple` of them representing the subscript[s]. 

1259 

1260 Attributes: 

1261 variable: variable to be typecast 

1262 subscript: a :class:`VariableValue` or :obj:`int` or :obj:`list` or 

1263 `tuple` of them representing the subscript[s]. 

1264 

1265 Examples: 

1266 >>> from csnake import Variable, Subscript 

1267 >>> var1 = Variable("var1", "int") 

1268 >>> subscr1_var1 = Subscript(var1, 3) 

1269 >>> var2 = Variable("var2", "int", value=subscr1_var1) 

1270 >>> print(var2) 

1271 int var2 = var1[3]; 

1272 >>> subscr2_var1 = Subscript(var1, (3, 2)) 

1273 >>> var3 = Variable("var3", "int", value=subscr2_var1) 

1274 >>> print(var3) 

1275 int var3 = var1[3][2]; 

1276 >>> subscr2_var1 = Subscript(var1, (var3, 2)) 

1277 >>> var4 = Variable("var4", "int", value=subscr2_var1) 

1278 >>> print(var4) 

1279 int var4 = var1[var3][2]; 

1280 """ 

1281 

1282 __slots__ = ("subscript", "variable") 

1283 

1284 def __init__( 

1285 self, variable: Union[CLiteral, VariableValue], subscript: Iterable 

1286 ) -> None: 

1287 if not isinstance(variable, VariableValue): 

1288 raise TypeError("variable must be VariableValue.") 

1289 self.variable = variable 

1290 

1291 if not isinstance( 

1292 subscript, (CIntLiteral, Iterable, VariableValue, CArrayLiteral) 

1293 ): 

1294 raise TypeError( 

1295 "Subscript must be an CIntLiteral, VariableValue " 

1296 "(or Variable), list or tuple." 

1297 ) 

1298 

1299 if not subscript: 

1300 raise TypeError("Subscript must be non-empty.") 

1301 

1302 try: 

1303 self.subscript = list(subscript) 

1304 except TypeError: 

1305 self.subscript = [subscript] 

1306 

1307 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1308 """Return a :obj:`str` used for initialization of other 

1309 :obj:`Variable`\\ s""" 

1310 

1311 return generate_c_value_initializer( 

1312 self.variable, formatters 

1313 ) + "".join( 

1314 f"[{generate_c_value_initializer(dim, formatters)}]" 

1315 for dim in self.subscript 

1316 ) 

1317 

1318 def __str__(self): 

1319 """Initialization string.""" 

1320 return self.init_target_code() 

1321 

1322 

1323class Dot(VariableValue): 

1324 """Dot (.) VariableValue for variable initialization. 

1325 

1326 Subclass of :class:`VariableValue` that returns an initialization string 

1327 for accessing a specific member of a struct. 

1328 

1329 Args: 

1330 variable: variable whose member we're accessing 

1331 member: name of member we're accessing 

1332 

1333 Attributes: 

1334 variable: variable whose member we're accessing 

1335 member: name of member we're accessing 

1336 

1337 Examples: 

1338 >>> from csnake import Variable, Dot 

1339 >>> var1 = Variable("var1", "struct somestr") 

1340 >>> dotvar = Dot(var1, 'some_member') 

1341 >>> var2 = Variable("var2", "int", value=dotvar) 

1342 >>> print(var2) 

1343 int var2 = var1.some_member; 

1344 """ 

1345 

1346 __slots__ = ("variable", "member") 

1347 

1348 def __init__( 

1349 self, variable: Union[CLiteral, VariableValue], member: str 

1350 ) -> None: 

1351 if not isinstance(variable, VariableValue): 

1352 raise TypeError("variable must be VariableValue.") 

1353 self.variable = variable 

1354 

1355 if not isinstance(member, (VariableValue, str)): 

1356 raise TypeError("member must be either a VariableValue or a str.") 

1357 self.member = member 

1358 

1359 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1360 """Return a :obj:`str` used for initialization of other 

1361 :obj:`Variable`\\ s""" 

1362 if isinstance(self.member, str): 

1363 return ( 

1364 self.variable.init_target_code(formatters) + "." + self.member 

1365 ) 

1366 elif isinstance(self.member, VariableValue): 

1367 return ( 

1368 self.variable.init_target_code(formatters) 

1369 + "." 

1370 + self.member.init_target_code(formatters) 

1371 ) 

1372 

1373 def __str__(self): 

1374 """Initialization string.""" 

1375 return self.init_target_code() 

1376 

1377 

1378class Arrow(VariableValue): 

1379 """Arrow (->) VariableValue for variable initialization. 

1380 

1381 Subclass of :class:`VariableValue` that returns an initialization string 

1382 for accessing a specific member of a struct indirectly (through a pointer). 

1383 

1384 Args: 

1385 variable: variable whose member we're accessing 

1386 member: name of member we're accessing 

1387 

1388 Attributes: 

1389 variable: variable whose member we're accessing 

1390 member: name of member we're accessing 

1391 

1392 Examples: 

1393 >>> from csnake import Variable, Arrow 

1394 >>> var1 = Variable("var1", "struct somestr") 

1395 >>> arrvar = Arrow(var1, 'some_member') 

1396 >>> var2 = Variable("var2", "int", value=arrvar) 

1397 >>> print(var2) 

1398 int var2 = var1->some_member; 

1399 """ 

1400 

1401 __slots__ = "variable" "item" 

1402 

1403 def __init__( 

1404 self, variable: Union[CLiteral, VariableValue], item: str 

1405 ) -> None: 

1406 if not isinstance(variable, VariableValue): 

1407 raise TypeError("variable must be VariableValue.") 

1408 self.variable = variable 

1409 

1410 if not isinstance(item, (VariableValue, str)): 

1411 raise TypeError("item must be either a VariableValue or a str.") 

1412 self.item = item 

1413 

1414 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1415 """Return a :obj:`str` used for initialization of other 

1416 :obj:`Variable`\\ s""" 

1417 if isinstance(self.item, str): 

1418 return ( 

1419 self.variable.init_target_code(formatters) + "->" + self.item 

1420 ) 

1421 elif isinstance(self.item, VariableValue): 

1422 return ( 

1423 self.variable.init_target_code(formatters) 

1424 + "->" 

1425 + self.item.init_target_code(formatters) 

1426 ) 

1427 

1428 def __str__(self): 

1429 """Initialization string.""" 

1430 return self.init_target_code() 

1431 

1432 

1433class GenericModifier(VariableValue): 

1434 """VariableValue generated by applying a function to a value. 

1435 

1436 A value's initializer string is passed through the supplied function, which 

1437 should return a :obj:`str`. 

1438 

1439 Args: 

1440 value: value whose initializer we're passing through a function 

1441 format_function: function used for modifying a value's initializer 

1442 

1443 Attributes: 

1444 value: value whose initializer we're passing through a function 

1445 format_function: function used for modifying a value's initializer 

1446 

1447 Examples: 

1448 >>> from csnake import Variable, GenericModifier 

1449 >>> var1 = Variable("var1", "int") 

1450 >>> genmod_var1 = GenericModifier(var1, lambda l: f'TRANSLATE({l})') 

1451 >>> var2 = Variable("var2", "int", value=genmod_var1) 

1452 >>> print(var2) 

1453 int var2 = TRANSLATE(var1); 

1454 >>> genmod_var2 = GenericModifier('test', lambda l: f'do_whatever({l})') 

1455 >>> var3 = Variable("var3", "int", value=genmod_var2) 

1456 >>> print(var3) 

1457 int var3 = do_whatever("test"); 

1458 """ 

1459 

1460 __slots__ = ("value", "format_function") 

1461 

1462 def __init__( 

1463 self, value: Union[CExtendedLiteral, VariableValue], function: Callable 

1464 ) -> None: 

1465 if not isinstance(value, (CExtendedLiteral, VariableValue)): 

1466 raise TypeError( 

1467 "value must be of type VariableValue or CExtendedLiteral." 

1468 ) 

1469 self.value = value 

1470 self.format_function = function 

1471 

1472 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1473 """Return a :obj:`str` used for initialization of other 

1474 :obj:`Variable`\\ s""" 

1475 return self.format_function(generate_c_value_initializer(self.value)) 

1476 

1477 def __str__(self): 

1478 """Initialization string.""" 

1479 return self.init_target_code() 

1480 

1481 

1482class OffsetOf(VariableValue): 

1483 """offsetof VariableValue modifier for initializing to offsets of struct 

1484 members. 

1485 

1486 Subclass of :class:`VariableValue` that returns an initialization string 

1487 containing the offset of a specific member of a struct. 

1488 

1489 

1490 Args: 

1491 struct: struct whose member's offset we're using 

1492 member: struct member whose offset we're using 

1493 

1494 Attributes: 

1495 struct: struct whose member's offset we're using 

1496 member: struct member whose offset we're using 

1497 

1498 Examples: 

1499 >>> from csnake import Variable, OffsetOf, Struct 

1500 >>> offs_val1 = OffsetOf('struct some', 'some_member') 

1501 >>> var2 = Variable("var2", "int", value=offs_val1 ) 

1502 >>> print(var2) 

1503 int var2 = offsetof(struct some, some_member); 

1504 >>> test_struct = Struct('other') 

1505 >>> offs_val2 = OffsetOf(test_struct, 'other_member') 

1506 >>> var3 = Variable("var3", "int", value=offs_val2 ) 

1507 >>> print(var3) 

1508 int var3 = offsetof(struct other, other_member); 

1509 """ 

1510 

1511 __slots__ = ("struct", "member") 

1512 

1513 def __init__( 

1514 self, struct: Union[Struct, str], member: Union[VariableValue, str] 

1515 ) -> None: 

1516 if not isinstance(struct, (str, Struct)): 

1517 raise TypeError("First argument must be either a Struct or a str.") 

1518 self.struct = struct 

1519 

1520 if not isinstance(member, (VariableValue, str)): 

1521 raise TypeError( 

1522 "Second argument must be either a VariableValue or a str" 

1523 ) 

1524 self.member = member 

1525 

1526 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1527 """Return a :obj:`str` used for initialization of other 

1528 :obj:`Variable`\\ s""" 

1529 if isinstance(self.struct, str): 

1530 struct_name = self.struct 

1531 elif isinstance(self.struct, Struct): 

1532 if self.struct.typedef: 

1533 struct_name = self.struct.name 

1534 else: 

1535 struct_name = "struct " + self.struct.name 

1536 

1537 if isinstance(self.member, str): 

1538 member_name = self.member 

1539 elif isinstance(self.member, VariableValue): 

1540 member_name = self.member.init_target_code(formatters) 

1541 

1542 return f"offsetof({struct_name}, {member_name})" 

1543 

1544 def __str__(self): 

1545 """Initialization string.""" 

1546 return self.init_target_code() 

1547 

1548 

1549class TextModifier(VariableValue): 

1550 """Generic textual VariableValue for initializing to an arbitrary 

1551 :obj:`str`. 

1552 

1553 The :obj:`str` supplied as the argument is output verbatim as the 

1554 Initialization string. 

1555 

1556 Args: 

1557 text: initialization string 

1558 

1559 Attributes: 

1560 text: initialization string 

1561 

1562 Examples: 

1563 >>> from csnake import Variable, TextModifier 

1564 >>> textmod1 = TextModifier('whatever + you + want') 

1565 >>> var2 = Variable("var2", "int", value=textmod1) 

1566 >>> print(var2) 

1567 int var2 = whatever + you + want; 

1568 >>> textmod2 = TextModifier(f'you_can_do_this_too + {var2.name}') 

1569 >>> var3 = Variable("var3", "int", value=textmod2) 

1570 >>> print(var3) 

1571 int var3 = you_can_do_this_too + var2; 

1572 """ 

1573 

1574 __slots__ = "text" 

1575 

1576 def __init__(self, text: str) -> None: 

1577 self.text = assure_str(text) 

1578 

1579 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1580 """Return a :obj:`str` used for initialization of other 

1581 :obj:`Variable`\\ s""" 

1582 return self.text 

1583 

1584 def __str__(self): 

1585 """Initialization string.""" 

1586 return self.init_target_code() 

1587 

1588 

1589class Function: 

1590 """Class describing C function. 

1591 

1592 You can generate a function prototype (declaration), a definition or a 

1593 call. 

1594 A function's body is a :class:`CodeWriterLite`, so You can add lines of 

1595 code to it (or a whole new :class:`CodeWriter` instance). 

1596 

1597 Args: 

1598 name: name of the function 

1599 return_type: :obj:`str` containing function's return type 

1600 

1601 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the 

1602 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...) 

1603 

1604 arguments: an iterable which yields one the following types: 

1605 

1606 * a :class:`Variable` 

1607 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings 

1608 (`name`, `primitive`) 

1609 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`, 

1610 `primitive`) 

1611 

1612 

1613 Attributes: 

1614 name: name of the function 

1615 return_type: :obj:`str` containing function's return type 

1616 

1617 qualifiers: :obj:`str` or :class:`Sequence` of :obj:`str` listing the 

1618 function's qualifiers (:ccode:`const`, :ccode:`volatile`, ...) 

1619 

1620 arguments: an iterable which yields one the following types: 

1621 

1622 * a :class:`Variable` 

1623 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings 

1624 (`name`, `primitive`) 

1625 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`, 

1626 `primitive`) 

1627 

1628 codewriter: internal instance of :class:`CodeWriterLite` that contains 

1629 the funtion's body code. 

1630 """ 

1631 

1632 __slots__ = ( 

1633 "name", 

1634 "return_type", 

1635 "arguments", 

1636 "qualifiers", 

1637 "codewriter", 

1638 ) 

1639 

1640 def __init__( 

1641 self, 

1642 name: str, 

1643 return_type: str = "void", 

1644 qualifiers: Union[str, Iterable[str]] = None, 

1645 arguments: Iterable = None, 

1646 ) -> None: 

1647 self.name = assure_str(name) 

1648 self.return_type = assure_str(return_type) 

1649 self.arguments: List[Variable] = [] 

1650 self.codewriter = CodeWriterLite() 

1651 

1652 if isinstance(qualifiers, str): 

1653 self.qualifiers = qualifiers.split() 

1654 elif qualifiers is not None: 

1655 self.qualifiers = [ 

1656 assure_str(qualifier) for qualifier in qualifiers 

1657 ] 

1658 else: 

1659 self.qualifiers = [] 

1660 

1661 if arguments is not None: 

1662 for arg in arguments: 

1663 proc_arg = _get_variable(arg) 

1664 self.add_argument(proc_arg) 

1665 

1666 def add_argument(self, arg: Variable) -> None: 

1667 """Add an argument to function. 

1668 

1669 Arguments are added sequentially. 

1670 

1671 arg: an one the following types: 

1672 

1673 * a :class:`Variable` 

1674 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings 

1675 (`name`, `primitive`) 

1676 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`, 

1677 `primitive`) 

1678 """ 

1679 

1680 proc_arg = _get_variable(arg) 

1681 

1682 self.arguments.append(proc_arg) 

1683 

1684 def add_arguments(self, args: Iterable[Variable]) -> None: 

1685 """Add multiple arguments to function. 

1686 

1687 Arguments are added sequentially. 

1688 

1689 args: an iterable which yields one the following types: 

1690 

1691 * a :class:`Variable` 

1692 * a :class:`Collection` (:obj:`tuple`/:obj:`list`-like) of 2 strings 

1693 (`name`, `primitive`) 

1694 * a :class:`Mapping` (:obj:`dict`-like) with keys (`name`, 

1695 `primitive`) 

1696 """ 

1697 

1698 for arg in args: 

1699 self.add_argument(arg) 

1700 

1701 def add_code(self, code: Union[str, Iterable[str]]) -> None: 

1702 """Add a :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to function's body 

1703 

1704 Since a :class:`CodeWriter` is an iterable of :obj:`str`\\ s, you can 

1705 simply add its contents to the function by passing it to this method. 

1706 

1707 

1708 Args: 

1709 code: :obj:`str` or :obj:`Iterable` of :obj:`str`\\ s to be added 

1710 """ 

1711 

1712 self.codewriter.add_lines(code) 

1713 

1714 def generate_call(self, *arg) -> str: 

1715 """Return function calling code for specific arguments. 

1716 

1717 Args: 

1718 \\*arg: argument values for call, in order of appearance. 

1719 

1720 Doesn't end with a semicolon (;). 

1721 

1722 Examples: 

1723 >>> from csnake import Variable, Function 

1724 >>> arg1 = Variable("arg1", "int") 

1725 >>> arg2 = Variable("arg2", "int", value=range(10)) 

1726 >>> arg3 = ("arg3", "int") 

1727 >>> arg4 = {"name": "arg4", "primitive": "int"} 

1728 >>> fun = Function( 

1729 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4) 

1730 ... ) 

1731 >>> fun.add_code(("code;", "more_code;")) 

1732 >>> print(fun.generate_call(1, 2, 3, 4)) 

1733 testfunct(1, 2, 3, 4) 

1734 """ 

1735 

1736 # if the arguments are all given in a single iterable 

1737 if len(arg) == 1 and isinstance(next(iter(arg)), Iterable): 

1738 return self.generate_call(*next(iter(arg))) 

1739 

1740 if not len(arg) == len(self.arguments): 

1741 raise ValueError("number of arguments must match") 

1742 

1743 arg_str = ", ".join(str(argument) for argument in arg) 

1744 return f"{self.name}({arg_str})" 

1745 

1746 def generate_prototype(self, extern: bool = False) -> str: 

1747 """Generate function prototype code. 

1748 

1749 Args: 

1750 extern: whether or not the function is to be declared as 

1751 :ccode:`extern` 

1752 

1753 Doesn't end with a semicolon (;). 

1754 

1755 Examples: 

1756 >>> from csnake import Variable, Function 

1757 >>> arg1 = Variable("arg1", "int") 

1758 >>> arg2 = Variable("arg2", "int", value=range(10)) 

1759 >>> arg3 = ("arg3", "int") 

1760 >>> arg4 = {"name": "arg4", "primitive": "int"} 

1761 >>> fun = Function( 

1762 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4) 

1763 ... ) 

1764 >>> fun.add_code(("code;", "more_code;")) 

1765 >>> print(fun.generate_prototype()) 

1766 void testfunct(int arg1, int arg2[10], int arg3, int arg4) 

1767 """ 

1768 return "{extern}{qual}{ret} {nm}({args})".format( 

1769 extern="extern " if extern else "", 

1770 qual=" ".join(self.qualifiers) + " " if self.qualifiers else "", 

1771 ret=self.return_type, 

1772 nm=self.name, 

1773 args=", ".join( 

1774 var.generate_declaration() for var in self.arguments 

1775 ) 

1776 if self.arguments 

1777 else "void", 

1778 ) 

1779 

1780 @property 

1781 def prototype(self) -> str: 

1782 """Function prototype (declaration) :obj:`str`. 

1783 

1784 Ends with a semicolon (;). 

1785 

1786 See Also: 

1787 :meth:`generate_prototype` for the underlying method. 

1788 """ 

1789 return self.generate_prototype() + ";" 

1790 

1791 def generate_definition( 

1792 self, indent: Union[int, str] = " " 

1793 ) -> CodeWriterLite: 

1794 """Return a :class:`CodeWriterLite` instance containing the 

1795 definition code for this :class:`Function`. 

1796 

1797 Args: 

1798 indent: indent :obj:`str` or :obj:`int` denoting the number of 

1799 spaces for indentation 

1800 

1801 Examples: 

1802 >>> from csnake import Variable, Function 

1803 >>> arg1 = Variable("arg1", "int") 

1804 >>> arg2 = Variable("arg2", "int", value=range(10)) 

1805 >>> arg3 = ("arg3", "int") 

1806 >>> arg4 = {"name": "arg4", "primitive": "int"} 

1807 >>> fun = Function( 

1808 ... "testfunct", "void", arguments=(arg1, arg2, arg3, arg4) 

1809 ... ) 

1810 >>> fun.add_code(("code;", "more_code;")) 

1811 >>> print(fun.generate_definition()) 

1812 void testfunct(int arg1, int arg2[10], int arg3, int arg4) 

1813 { 

1814 code; 

1815 more_code; 

1816 } 

1817 

1818 """ 

1819 writer = CodeWriterLite(indent=indent) 

1820 writer.add_line(self.generate_prototype()) 

1821 writer.open_brace() 

1822 

1823 writer.add_lines(self.codewriter) # type: ignore 

1824 

1825 writer.close_brace() 

1826 

1827 return writer 

1828 

1829 @property 

1830 def definition(self) -> str: 

1831 """Function definition :obj:`str`. 

1832 

1833 See Also: 

1834 :meth:`generate_definition` for the underlying method. 

1835 """ 

1836 return self.generate_definition().code 

1837 

1838 def init_target_code(self, formatters: LiteralFormatters = None) -> str: 

1839 """Return code used for variable initialization, formatted with the 

1840 supplied formatters. 

1841 

1842 Args: 

1843 formatters: collection of formatters used for formatting the 

1844 initialization :obj:`str` 

1845 """ 

1846 return self.name 

1847 

1848 

1849# TODO: allow for functions to be used to initialize function pointers 

1850 

1851VariableValue.register(Function) 

1852 

1853 

1854class Enum: 

1855 """Class describing C :ccode:`enum` construct. 

1856 

1857 Args: 

1858 name: name of the enum 

1859 prefix: prefix to add to every enumeration's name 

1860 typedef: whether or not the enum is :ccode:`typedef`'d 

1861 

1862 Attributes: 

1863 name: name of the enum 

1864 typedef: whether or not the enum is :ccode:`typedef`'d 

1865 prefix: prefix to add to every enumeration's name 

1866 values: `list` of :class:`Enum.EnumValue` 

1867 """ 

1868 

1869 __slots__ = ("typedef", "values", "name", "prefix") 

1870 

1871 class EnumValue: 

1872 """Singular value of an C-style enumeration.""" 

1873 

1874 __slots__ = ("name", "value", "comment") 

1875 

1876 def __init__( 

1877 self, 

1878 name: str, 

1879 value: Union[CLiteral, VariableValue] = None, 

1880 comment: str = None, 

1881 ) -> None: 

1882 self.name = assure_str(name) 

1883 if value is not None and not isinstance( 

1884 value, (CLiteral, VariableValue) 

1885 ): 

1886 raise ValueError( 

1887 f"value ({value}) must be one of (CLiteral, VariableValue)" 

1888 ) 

1889 self.value = value 

1890 self.comment = assure_str(comment) if comment is not None else None 

1891 

1892 def __init__( 

1893 self, name: str, prefix: str = None, typedef: bool = False 

1894 ) -> None: 

1895 

1896 self.typedef = bool(typedef) 

1897 # enum values 

1898 self.values: List[Enum.EnumValue] = [] 

1899 self.name = assure_str(name) 

1900 

1901 if prefix is not None: 

1902 self.prefix = assure_str(prefix) 

1903 else: 

1904 self.prefix = "" 

1905 

1906 def add_value( 

1907 self, 

1908 name: str, 

1909 value: Union[CLiteral, VariableValue] = None, 

1910 comment: str = None, 

1911 ) -> None: 

1912 """Add a single :ccode:`name = value` pair (or just :ccode:`name`).""" 

1913 self.values.append(self.EnumValue(name, value=value, comment=comment)) 

1914 

1915 def add_values( 

1916 self, 

1917 values: Union[Mapping, Collection[Union[Mapping, Collection, str]]], 

1918 ) -> None: 

1919 """Add multiple :ccode:`name = value` pairs (or just :ccode:`name`\\ s). 

1920 

1921 Values can be one of: 

1922 

1923 * a :obj:`dict` with `name` : `value` entries 

1924 * a collection of: 

1925 * :obj:`str`\\ s denoting names (no value) 

1926 * :obj:`dict`\\ s with keys [`name`, `value`, `comment`], and optional 

1927 keys [`value`, `comment`] 

1928 * collections (:obj:`list`-like) of length 1-3 denoting [`name`, 

1929 `value`, `comment`] respectively 

1930 

1931 """ 

1932 if isinstance(values, Mapping): 

1933 for name, value in values.items(): 

1934 self.add_value(name, value) 

1935 

1936 else: 

1937 for value in values: 

1938 if isinstance(value, str): 

1939 self.add_value(value) 

1940 elif isinstance(value, Mapping): 

1941 self.add_value(**value) 

1942 # lists and tuples 

1943 else: 

1944 defaults = (None, None, None) 

1945 name = seq_get(value, defaults, 0) 

1946 value = seq_get(value, defaults, 1) 

1947 comment = seq_get(value, defaults, 2) 

1948 

1949 name = assure_str(name) 

1950 

1951 self.add_value(name, value, comment) 

1952 

1953 def generate_declaration( 

1954 self, indent: Union[int, str] = 4 

1955 ) -> CodeWriterLite: 

1956 """Generate enum declaration code. 

1957 

1958 Generate a :class:`CodeWriterLite` instance containing the 

1959 initialization code for this :class:`Enum`. 

1960 

1961 Args: 

1962 indent: indent :obj:`str` or :obj:`int` denoting the number of 

1963 spaces for indentation 

1964 

1965 

1966 Examples: 

1967 >>> from csnake import ( 

1968 ... Enum, Variable, Dereference, AddressOf 

1969 ... ) 

1970 >>> name = "somename" 

1971 >>> pfx = "pfx" 

1972 >>> typedef = False 

1973 >>> enum = Enum(name, prefix=pfx, typedef=typedef) 

1974 >>> cval1 = Variable("varname", "int") 

1975 >>> enum.add_value("val1", 1) 

1976 >>> enum.add_value("val2", Dereference(1000)) 

1977 >>> enum.add_value("val3", cval1) 

1978 >>> enum.add_value("val4", AddressOf(cval1), "some comment") 

1979 >>> print(enum.generate_declaration()) 

1980 enum somename 

1981 { 

1982 pfxval1 = 1, 

1983 pfxval2 = *1000, 

1984 pfxval3 = varname, 

1985 pfxval4 = &varname /* some comment */ 

1986 }; 

1987 """ 

1988 

1989 gen = CodeWriterLite(indent=indent) 

1990 

1991 if self.typedef: 

1992 gen.add_line("typedef enum") 

1993 else: 

1994 gen.add_line("enum {name}".format(name=self.name)) 

1995 gen.open_brace() 

1996 

1997 def _generate_lines( 

1998 values: Iterable, lastline: bool = False 

1999 ) -> Iterator: 

2000 return ( 

2001 ( 

2002 "{prefix}{name}{value}{comma}".format( 

2003 prefix=self.prefix, 

2004 name=assure_str(val.name), 

2005 value=(" = " + generate_c_value_initializer(val.value)) 

2006 if val.value 

2007 else "", 

2008 comma="," if not lastline else "", 

2009 ), 

2010 val.comment, 

2011 ) 

2012 for val in values 

2013 ) 

2014 

2015 commalines = _generate_lines(self.values[:-1], lastline=False) 

2016 lastline = _generate_lines(self.values[-1:], lastline=True) 

2017 

2018 for line in chain(commalines, lastline): 

2019 gen.add_line(*line) 

2020 

2021 gen.close_brace() 

2022 

2023 if self.typedef: 

2024 gen.add(" " + self.name + ";") 

2025 else: 

2026 gen.add(";") 

2027 

2028 return gen 

2029 

2030 @property 

2031 def declaration(self) -> str: 

2032 """The declaration :obj:`str` (with indentation of 4 spaces). 

2033 

2034 

2035 See Also: 

2036 :meth:`generate_declaration` for the underlying method. 

2037 """ 

2038 return self.generate_declaration().code 

2039 

2040 def __str__(self) -> str: 

2041 """ 

2042 Return the declaration :obj:`str` (with indentation of 4 spaces). 

2043 """ 

2044 return self.declaration