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 

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

7# of using another instance every time. 

8# TODO: add typedefs, especially for function pointers 

9import collections 

10from abc import ABCMeta 

11from abc import abstractmethod 

12from collections import deque 

13from collections import namedtuple 

14from itertools import chain 

15from typing import Callable 

16from typing import Collection 

17from typing import Iterable 

18from typing import Iterator 

19from typing import List 

20from typing import Mapping 

21from typing import Optional 

22from typing import Tuple 

23from typing import Union 

24 

25from .codewriterlite import CodeWriterLite 

26from .utils import assure_str 

27from .utils import seq_get 

28 

29# ruff: noqa: B024 

30 

31 

32# internal types used by csnake as initialization values 

33class CIntLiteral(metaclass=ABCMeta): 

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

35 

36 

37CIntLiteral.register(int) 

38 

39 

40class CFloatLiteral(metaclass=ABCMeta): 

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

42 

43 

44CFloatLiteral.register(float) 

45 

46 

47class CArrayLiteral(metaclass=ABCMeta): 

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

49 

50 @classmethod 

51 def __subclasshook__(cls, subclass): 

52 if cls is CArrayLiteral: 

53 if ( 

54 issubclass(subclass, collections.abc.Sized) 

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

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

57 ): 

58 return True 

59 return False 

60 return NotImplemented 

61 

62 

63CArrayLiteral.register(list) 

64CArrayLiteral.register(tuple) 

65 

66 

67class CStructLiteral(metaclass=ABCMeta): 

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

69 

70 @classmethod 

71 def __subclasshook__(cls, subclass): 

72 if cls is CStructLiteral: 

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

74 return NotImplemented 

75 

76 

77CStructLiteral.register(dict) 

78 

79 

80class CBasicLiteral(metaclass=ABCMeta): 

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

82 

83 

84CBasicLiteral.register(CIntLiteral) 

85CBasicLiteral.register(CFloatLiteral) 

86 

87 

88class CExtendedLiteral(metaclass=ABCMeta): 

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

90 

91 

92CExtendedLiteral.register(CBasicLiteral) 

93CExtendedLiteral.register(bool) 

94CExtendedLiteral.register(str) 

95 

96 

97class CLiteral(metaclass=ABCMeta): 

98 """ABC for any C literal.""" 

99 

100 @classmethod 

101 def __subclasshook__(cls, subclass): 

102 if cls is CLiteral: 

103 return issubclass( 

104 subclass, (CExtendedLiteral, CStructLiteral, CArrayLiteral) 

105 ) 

106 return NotImplemented 

107 

108 

109CLiteral.register(CExtendedLiteral) 

110CLiteral.register(CStructLiteral) 

111 

112# conditional imports 

113try: 

114 import numpy as np 

115except ImportError: 

116 pass 

117else: 

118 CIntLiteral.register(np.integer) 

119 CFloatLiteral.register(np.floating) 

120 

121try: 

122 import sympy as sp 

123except ImportError: 

124 pass 

125else: 

126 CIntLiteral.register(sp.Integer) 

127 CFloatLiteral.register(sp.Float) 

128 

129 

130class ShapelessError(TypeError): 

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

132 

133 

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

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

136 

137 try: 

138 return array.shape # type: ignore 

139 except AttributeError: 

140 pass 

141 

142 if isinstance(array, str): 

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

144 

145 if isinstance(array, Mapping): 

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

147 

148 curr = array 

149 shp: List[int] = [] 

150 

151 while True: 

152 if not isinstance(curr, CArrayLiteral): 

153 return tuple(shp) 

154 try: 

155 shp.append(len(curr)) 

156 itr = iter(curr) 

157 curr = next(itr) 

158 except (TypeError, IndexError): 

159 return tuple(shp) 

160 

161 

162# C constructs: 

163 

164 

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

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

167 

168 Args: 

169 value: boolean to format 

170 

171 Returns: 

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

173 """ 

174 if value: 

175 return "true" 

176 return "false" 

177 

178 

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

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

181 

182 Args: 

183 value: string to format 

184 

185 Returns: 

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

187 """ 

188 return f'"{value}"' 

189 

190 

191class LiteralFormatters: 

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

193 

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

195 

196 Args: 

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

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

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

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

201 

202 Attributes: 

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

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

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

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

207 """ 

208 

209 __slots__ = ( 

210 "int_formatter", 

211 "float_formatter", 

212 "string_formatter", 

213 "bool_formatter", 

214 ) 

215 

216 def __init__( 

217 self, 

218 int_formatter: Callable = int, 

219 float_formatter: Callable = float, 

220 bool_formatter: Callable = simple_bool_formatter, 

221 string_formatter: Callable = simple_str_formatter, 

222 ) -> None: 

223 self.int_formatter = int_formatter 

224 self.float_formatter = float_formatter 

225 self.bool_formatter = bool_formatter 

226 self.string_formatter = string_formatter 

227 

228 @classmethod 

229 def empty(cls): 

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

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

232 

233 def __repr__(self): 

234 """Simple printout of parameter values.""" 

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

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

237 

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

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

240 

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

242 arg = next(iter(args)) 

243 if isinstance(arg, LiteralFormatters): 

244 initializer = { 

245 slot: getattr(arg, slot) 

246 if getattr(arg, slot) is not None 

247 else getattr(self, slot) 

248 for slot in self.__slots__ 

249 } 

250 

251 return LiteralFormatters(**initializer) 

252 

253 if isinstance(arg, Mapping): 

254 return self.replace(**arg) 

255 

256 if kwargs and not args: 

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

258 if diff: 

259 raise TypeError( 

260 f"Arguments {diff!s} are invalid for this function" 

261 ) 

262 

263 initializer = { 

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

265 for slot in self.__slots__ 

266 } 

267 

268 return LiteralFormatters(**initializer) 

269 

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

271 

272 @classmethod 

273 def partial(cls, dictionary: Mapping): 

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

275 rtnval = cls.empty() 

276 return rtnval.replace(dictionary) 

277 

278 

279class FormattedLiteral: 

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

281 

282 The accompanying literal formatter collection will be used when generating 

283 the initialization :obj:`str`. 

284 

285 Args: 

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

287 ['int_formatter', 'float_formatter', 'bool_formatter', 

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

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

290 

291 Attributes: 

292 value: value of literal 

293 formatter: collection of literal formatters 

294 """ 

295 

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

297 

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

299 self.value = value 

300 if args: 

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

302 

303 self.formatter = LiteralFormatters.partial(kwargs) 

304 

305 

306class VariableValue(metaclass=ABCMeta): 

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

308 

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

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

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

312 modifiers. 

313 """ 

314 

315 @abstractmethod 

316 def init_target_code(self, formatters: Optional[LiteralFormatters] = None): 

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

318 supplied formatters. 

319 

320 Args: 

321 formatters: collection of formatters used for formatting the 

322 initialization :obj:`str` 

323 """ 

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: Optional[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 writer.add("{") 

501 writer.indent() 

502 

503 try: 

504 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack) 

505 except IndexError: 

506 pass 

507 else: 

508 if isinstance( 

509 lookahead, 

510 (_ArrStructInitGen.OpenBrace, CArrayLiteral, CStructLiteral), 

511 ): 

512 writer.add_line("") 

513 return formatters 

514 

515 @staticmethod 

516 def literal_or_value_handler( 

517 top, 

518 stack: deque, 

519 writer: CodeWriterLite, 

520 formatters: LiteralFormatters, 

521 ) -> LiteralFormatters: 

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

523 

524 try: 

525 lookahead = _ArrStructInitGen.lookahead_ignoring_format(stack) 

526 except IndexError: 

527 pass 

528 else: 

529 if isinstance( 

530 lookahead, 

531 ( 

532 CExtendedLiteral, 

533 VariableValue, 

534 _ArrStructInitGen.Designator, 

535 ), 

536 ): 

537 writer.add(",") 

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

539 writer.add(" ") 

540 return formatters 

541 

542 @staticmethod 

543 def designator_handler( 

544 top, 

545 stack: deque, 

546 writer: CodeWriterLite, 

547 formatters: LiteralFormatters, 

548 ) -> LiteralFormatters: 

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

550 return formatters 

551 

552 @staticmethod 

553 def formatted_literal_handler( 

554 top, 

555 stack: deque, 

556 writer: CodeWriterLite, 

557 formatters: LiteralFormatters, 

558 ) -> LiteralFormatters: 

559 new_formatters = formatters.replace(top.formatter) 

560 

561 stack.append(formatters) 

562 stack.append(top.value) 

563 stack.append(new_formatters) 

564 return formatters 

565 

566 @staticmethod 

567 def literal_formatters_handler( 

568 top, 

569 stack: deque, 

570 writer: CodeWriterLite, 

571 formatters: LiteralFormatters, 

572 ) -> LiteralFormatters: 

573 formatters = top 

574 return formatters 

575 

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

577 

578 

579_ArrStructInitGen.type_action_pairs = ( 

580 _ArrStructInitGen.TypeActionPair( 

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

582 ), 

583 _ArrStructInitGen.TypeActionPair( 

584 types=(CStructLiteral), 

585 action=_ArrStructInitGen.c_struct_literal_handler, 

586 ), 

587 _ArrStructInitGen.TypeActionPair( 

588 types=(_ArrStructInitGen.ClosedBrace), 

589 action=_ArrStructInitGen.closed_brace_handler, 

590 ), 

591 _ArrStructInitGen.TypeActionPair( 

592 types=(_ArrStructInitGen.OpenBrace), 

593 action=_ArrStructInitGen.open_brace_handler, 

594 ), 

595 _ArrStructInitGen.TypeActionPair( 

596 types=(CExtendedLiteral, VariableValue), 

597 action=_ArrStructInitGen.literal_or_value_handler, 

598 ), 

599 _ArrStructInitGen.TypeActionPair( 

600 types=(_ArrStructInitGen.Designator), 

601 action=_ArrStructInitGen.designator_handler, 

602 ), 

603 _ArrStructInitGen.TypeActionPair( 

604 types=(FormattedLiteral), 

605 action=_ArrStructInitGen.formatted_literal_handler, 

606 ), 

607 _ArrStructInitGen.TypeActionPair( 

608 types=(LiteralFormatters), 

609 action=_ArrStructInitGen.literal_formatters_handler, 

610 ), 

611) 

612 

613 

614class NoValueError(ValueError): 

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

616 

617 

618class FuncPtr: 

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

620 

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

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

623 

624 Args: 

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

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

627 

628 * a :class:`Variable` 

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

630 (`name`, `primitive`) 

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

632 `primitive`) 

633 """ 

634 

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

636 

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

638 

639 def __init__( 

640 self, 

641 return_type: str, 

642 arguments: Optional[Iterable] = None, 

643 ) -> None: 

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, 

653 name: str, 

654 qualifiers: Optional[str] = None, 

655 array: Optional[str] = None, 

656 ) -> str: 

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

658 

659 This method is meant to be called from 

660 :meth:`Variable.generate_declaration` and 

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

662 useless to You elsewhere. 

663 """ 

664 jointargs = ", ".join( 

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

666 ) 

667 

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

669 rt=self.return_type, 

670 qual=qualifiers if qualifiers else "", 

671 name=name, 

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

673 arr=array if array else "", 

674 ) 

675 

676 return retval 

677 

678 

679class Variable: 

680 """Class describing C variable contruct. 

681 

682 You can generate declarations and initializations for variables, and 

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

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

685 :ccode:`structs`, ...). 

686 

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

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

689 

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

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

692 and floats): 

693 

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

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

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

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

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

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

700 

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

702 for, no sense in replicating such functionality here. 

703 

704 Args: 

705 name: name of the variable 

706 

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

708 type 

709 

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

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

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

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

714 from `value`. 

715 

716 comment: accompanying comment 

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

718 above 

719 

720 """ 

721 

722 __slots__ = ( 

723 "name", 

724 "primitive", 

725 "qualifiers", 

726 "array", 

727 "comment", 

728 "value", 

729 ) 

730 

731 def __init__( 

732 self, 

733 name: str, 

734 primitive: Union[str, FuncPtr], 

735 qualifiers: Optional[Union[Iterable[str], str]] = None, 

736 array: Optional[Iterable] = None, 

737 comment: Optional[str] = None, 

738 value: Optional[CLiteral] = None, 

739 ) -> None: 

740 self.name = assure_str(name) 

741 if isinstance(primitive, FuncPtr): 

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

743 else: 

744 self.primitive = assure_str(primitive) 

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

746 self.array = array 

747 self.qualifiers = qualifiers 

748 self.value = value 

749 

750 @staticmethod 

751 def merge_formatters( 

752 value: CLiteral, formatters: Optional[LiteralFormatters] = None 

753 ) -> Tuple[CLiteral, LiteralFormatters]: 

754 formatters_ = formatters or LiteralFormatters() 

755 

756 while isinstance(value, FormattedLiteral): 

757 formatters_ = formatters_.replace(value.formatter) 

758 value = value.value 

759 

760 return (value, formatters_) 

761 

762 def __array_dimensions(self) -> str: 

763 value = self.value 

764 if value is not None: 

765 value, _ = self.merge_formatters(value) 

766 

767 if isinstance(self.array, CArrayLiteral): 

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

769 elif self.array is not None: 

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

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

772 array = "[]" 

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

774 try: 

775 val_shape = _shape(value) 

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

777 except ShapelessError: 

778 array = "" 

779 else: 

780 array = "" 

781 

782 return array 

783 

784 def init_target_code( 

785 self, formatters: Optional[LiteralFormatters] = None 

786 ) -> str: 

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

788 :obj:`Variable`\\ s""" 

789 return self.name 

790 

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

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

793 

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

795 

796 Args: 

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

798 :ccode:`extern` 

799 

800 Examples: 

801 >>> import numpy as np 

802 >>> from csnake import Variable 

803 >>> var = Variable( 

804 ... "test", 

805 ... primitive="int", 

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

807 ... ) 

808 >>> print(var.generate_declaration()) 

809 int test[2][3][4] 

810 """ 

811 

812 if self.qualifiers is not None: 

813 if isinstance(self.qualifiers, str): 

814 qual = self.qualifiers + " " 

815 else: 

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

817 else: 

818 qual = "" 

819 

820 array = self.__array_dimensions() 

821 

822 if isinstance(self.primitive, FuncPtr): 

823 decl = self.primitive.get_declaration( 

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

825 ) 

826 

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

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

829 ) 

830 

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

832 ext="extern " if extern else "", 

833 qual=qual, 

834 prim=self.primitive, 

835 name=self.name, 

836 array=array, 

837 ) 

838 

839 @property 

840 def declaration(self) -> str: 

841 """Declaration string. 

842 

843 Ends with a semicolon (;). 

844 

845 See Also: 

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

847 """ 

848 return ( 

849 self.generate_declaration(extern=False) 

850 + ";" 

851 + (f" /* {self.comment} */" if self.comment is not None else "") 

852 ) 

853 

854 def __generate_array_struct_initialization( 

855 self, 

856 array: Union[CArrayLiteral, CStructLiteral], 

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

858 writer: Optional[CodeWriterLite] = None, 

859 formatters: Optional[LiteralFormatters] = None, 

860 ) -> None: 

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

862 

863 if self.value is None: 

864 raise NoValueError 

865 

866 stack: deque = deque() 

867 stack.append(array) 

868 if not formatters: 

869 formatters = LiteralFormatters() 

870 if writer is None: 

871 writer = CodeWriterLite(indent=indent) 

872 

873 while stack: 

874 top = stack.pop() 

875 for types, action in _ArrStructInitGen.type_action_pairs: 

876 if isinstance(top, types): 

877 formatters = action( 

878 top=top, 

879 stack=stack, 

880 writer=writer, 

881 formatters=formatters, 

882 ) 

883 break 

884 else: 

885 raise TypeError("Unknown type on stack") 

886 

887 def generate_initialization( 

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

889 ) -> CodeWriterLite: 

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

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

892 

893 Ends with a semicolon (;). 

894 

895 Args: 

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

897 spaces for indentation 

898 

899 

900 Example: 

901 >>> import numpy as np 

902 >>> from csnake import Variable 

903 >>> var = Variable( 

904 ... "test", 

905 ... primitive="int", 

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

907 ... ) 

908 >>> print(var.generate_initialization()) 

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

910 { 

911 {0, 1, 2, 3}, 

912 {4, 5, 6, 7}, 

913 {8, 9, 10, 11} 

914 }, 

915 { 

916 {12, 13, 14, 15}, 

917 {16, 17, 18, 19}, 

918 {20, 21, 22, 23} 

919 } 

920 }; 

921 

922 """ 

923 

924 # main part: generating initializer 

925 if self.value is None: 

926 raise NoValueError 

927 

928 writer = CodeWriterLite(indent=indent) 

929 

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

931 self.qualifiers, Iterable 

932 ): 

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

934 elif self.qualifiers is not None: 

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

936 else: 

937 qual = "" 

938 

939 array = self.__array_dimensions() 

940 

941 if isinstance(self.primitive, FuncPtr): 

942 decl = self.primitive.get_declaration( 

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

944 ) 

945 writer.add_line(decl) 

946 else: 

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

948 

949 writer.add(" = ") 

950 

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

952 

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

954 self.__generate_array_struct_initialization( 

955 value, indent, writer, formatters 

956 ) 

957 else: 

958 formatters = LiteralFormatters() 

959 assignment = generate_c_value_initializer(value, formatters) 

960 writer.add(assignment) 

961 

962 writer.add(";") 

963 return writer 

964 

965 @property 

966 def initialization(self) -> str: 

967 """Initialization :obj:`str`. 

968 

969 Ends with a semicolon (;). 

970 

971 See Also: 

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

973 """ 

974 return str(self.generate_initialization()) 

975 

976 def __str__(self): 

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

978 

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

980 

981 Ends with a semicolon (;). 

982 

983 See Also: 

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

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

986 """ 

987 try: 

988 return self.initialization 

989 except NoValueError: 

990 return self.declaration 

991 

992 

993# no VariableValue is also a VariableValue! 

994VariableValue.register(Variable) 

995 

996 

997class BitField: 

998 """Class describing C bit fields. 

999 

1000 Bit fields in C are a way of packing variables into bits. 

1001 

1002 the C syntax is: 

1003 :ccode:`type [name] : width ;` 

1004 

1005 where 

1006 * `type` is usually an integer type. 

1007 * `name` is optional so we could have unnamed bits for padding.. 

1008 * `width` is the number of bits. 

1009 

1010 Note: 

1011 This class made for declaring bit field specifics for use with 

1012 :meth:`Struct.add_bit_field`. 

1013 

1014 Args: 

1015 name: (Optional) name of the bit field 

1016 width: width of the bit field 

1017 primitive: primitive type of the bit field 

1018 comment: accompanying comment 

1019 """ 

1020 

1021 __slots__ = ( 

1022 "primitive", 

1023 "width", 

1024 "name", 

1025 "comment", 

1026 ) 

1027 

1028 def __init__( 

1029 self, 

1030 primitive: str, 

1031 width: int, 

1032 name: Optional[str] = None, 

1033 comment: Optional[str] = None, 

1034 ) -> None: 

1035 self.primitive = assure_str(primitive) 

1036 self.width = width 

1037 self.name = name 

1038 self.comment = comment 

1039 

1040 @property 

1041 def declaration(self) -> str: 

1042 """Declaration string. 

1043 

1044 Ends with a semicolon (;). 

1045 Includes the comment if present. 

1046 """ 

1047 

1048 retval = "{type} {name}: {width};".format( 

1049 type=self.primitive, 

1050 name=self.name if self.name is not None else "", 

1051 width=self.width, 

1052 ) 

1053 

1054 if self.comment is not None: 

1055 return f"{retval} /* {self.comment} */" 

1056 

1057 return retval 

1058 

1059 

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

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

1062 

1063 * a Variable (idempotent) 

1064 

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

1066 

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

1068 """ 

1069 if isinstance(variable, Variable): 

1070 return variable 

1071 if isinstance(variable, Mapping): 

1072 var = Variable(**variable) 

1073 return var 

1074 if isinstance(variable, Collection): 

1075 if len(variable) != 2: # noqa: PLR2004 

1076 raise TypeError( 

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

1078 ) 

1079 var = Variable(*variable) 

1080 return var 

1081 raise TypeError("variable must be one of (Variable, Collection, Mapping)") 

1082 

1083 

1084class Struct: 

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

1086 

1087 Args: 

1088 name: name of struct 

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

1090 

1091 Attributes: 

1092 name: name of struct 

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

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

1095 """ 

1096 

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

1098 

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

1100 self.name = assure_str(name) 

1101 self.variables: List[Union[Variable, BitField]] = [] 

1102 self.typedef = bool(typedef) 

1103 

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

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

1106 

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

1108 The order is synced with the :meth:`add_bit_field` function. 

1109 

1110 Args: 

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

1112 

1113 `variable` can be: 

1114 

1115 * a :class:`Variable` 

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

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

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

1119 'primitive'] 

1120 """ 

1121 

1122 proc_var = _get_variable(variable) 

1123 self.variables.append(proc_var) 

1124 

1125 def add_bit_field(self, bit_field: BitField): 

1126 """Add a bit field to `struct`. 

1127 

1128 Bit fields inside of a :class:`Struct` are ordered (added sequentially). 

1129 The order is synced with the :meth:`add_variable` function. 

1130 

1131 see :class:`BitField` for more information. 

1132 

1133 Args: 

1134 bit_field: bit field to add. 

1135 """ 

1136 self.variables.append(bit_field) 

1137 

1138 def generate_declaration( 

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

1140 ) -> CodeWriterLite: 

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

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

1143 

1144 Args: 

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

1146 spaces for indentation 

1147 

1148 Example: 

1149 >>> from csnake import BitField, Variable, Struct 

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

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

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

1153 >>> strct.add_variable(var1) 

1154 >>> strct.add_variable(var2) 

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

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

1157 >>> var5 = BitField("int", 2, name="var5") 

1158 >>> var6 = BitField("int", 3, comment="unused bits") 

1159 >>> var7 = BitField("int", 1, name="var7") 

1160 >>> strct.add_bit_field(var5) 

1161 >>> strct.add_bit_field(var6) 

1162 >>> strct.add_bit_field(var7) 

1163 >>> print(strct.generate_declaration()) 

1164 struct strname 

1165 { 

1166 int var1; 

1167 int var2[10]; 

1168 int var3; 

1169 int var4; 

1170 int var5: 2; 

1171 int : 3; /* unused bits */ 

1172 int var7: 1; 

1173 }; 

1174 """ 

1175 writer = CodeWriterLite(indent=indent) 

1176 

1177 if self.typedef: 

1178 writer.add_line("typedef struct") 

1179 else: 

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

1181 

1182 writer.open_brace() 

1183 for var in self.variables: 

1184 writer.add_line(var.declaration) 

1185 writer.close_brace() 

1186 

1187 if self.typedef: 

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

1189 else: 

1190 writer.add(";") 

1191 

1192 return writer 

1193 

1194 @property 

1195 def declaration(self): 

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

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

1198 

1199 See Also: 

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

1201 """ 

1202 

1203 return self.generate_declaration() 

1204 

1205 def __str__(self): 

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

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

1208 return str(self.generate_declaration()) 

1209 

1210 

1211class AddressOf(VariableValue): 

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

1213 

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

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

1216 

1217 Args: 

1218 variable: variable to return the address of. 

1219 

1220 Attributes: 

1221 variable: variable to return the address of. 

1222 

1223 Examples: 

1224 >>> from csnake import Variable, AddressOf 

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

1226 >>> addrof_var1 = AddressOf(var1) 

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

1228 >>> print(var2) 

1229 int var2 = &var1; 

1230 """ 

1231 

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

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

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

1235 self.variable = variable 

1236 

1237 def init_target_code( 

1238 self, formatters: Optional[LiteralFormatters] = None 

1239 ) -> str: 

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

1241 :obj:`Variable`\\ s""" 

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

1243 

1244 def __str__(self): 

1245 """Initialization string.""" 

1246 return self.init_target_code() 

1247 

1248 

1249class Dereference(VariableValue): 

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

1251 

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

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

1254 

1255 Args: 

1256 value: value to dereference. 

1257 

1258 Attributes: 

1259 value: value to dereference. 

1260 

1261 Examples: 

1262 >>> from csnake import Variable, Dereference 

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

1264 >>> derefd_var1 = Dereference(var1) 

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

1266 >>> print(var2) 

1267 int var2 = *var1; 

1268 >>> derefd_number = Dereference(16) 

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

1270 >>> print(var3) 

1271 int var3 = *16; 

1272 """ 

1273 

1274 __slots__ = ("value",) 

1275 

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

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

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

1279 self.value = value 

1280 

1281 def init_target_code( 

1282 self, formatters: Optional[LiteralFormatters] = None 

1283 ) -> str: 

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

1285 :obj:`value`\\ s""" 

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

1287 

1288 def __str__(self): 

1289 """Initialization string.""" 

1290 return self.init_target_code() 

1291 

1292 

1293class Typecast(VariableValue): 

1294 """Typecast modifier for variable initialization. 

1295 

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

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

1298 

1299 Args: 

1300 value: value to be typecast 

1301 cast: type to cast to 

1302 

1303 Attributes: 

1304 value: value to be typecast 

1305 cast: type to cast to 

1306 

1307 Examples: 

1308 >>> from csnake import Variable, Typecast 

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

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

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

1312 >>> print(var2) 

1313 int var2 = (long) var1; 

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

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

1316 >>> print(var3) 

1317 int var3 = (long) 16; 

1318 """ 

1319 

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

1321 

1322 def __init__( 

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

1324 ) -> None: 

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

1326 raise TypeError( 

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

1328 ) 

1329 self.value = value 

1330 self.cast = assure_str(cast) 

1331 

1332 def init_target_code( 

1333 self, formatters: Optional[LiteralFormatters] = None 

1334 ) -> str: 

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

1336 :obj:`value`\\ s""" 

1337 initializer = generate_c_value_initializer(self.value, formatters) 

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

1339 

1340 def __str__(self) -> str: 

1341 """Initialization string.""" 

1342 return self.init_target_code() 

1343 

1344 

1345class Subscript(VariableValue): 

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

1347 

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

1349 with a subscripted value. 

1350 

1351 Args: 

1352 variable: variable to be typecast 

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

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

1355 

1356 Attributes: 

1357 variable: variable to be typecast 

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

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

1360 

1361 Examples: 

1362 >>> from csnake import Variable, Subscript 

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

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

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

1366 >>> print(var2) 

1367 int var2 = var1[3]; 

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

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

1370 >>> print(var3) 

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

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

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

1374 >>> print(var4) 

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

1376 """ 

1377 

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

1379 

1380 def __init__( 

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

1382 ) -> None: 

1383 if not isinstance(variable, VariableValue): 

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

1385 self.variable = variable 

1386 

1387 if not isinstance( 

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

1389 ): 

1390 raise TypeError( 

1391 "Subscript must be an CIntLiteral, VariableValue " 

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

1393 ) 

1394 

1395 if not subscript: 

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

1397 

1398 try: 

1399 self.subscript = list(subscript) 

1400 except TypeError: 

1401 self.subscript = [subscript] 

1402 

1403 def init_target_code( 

1404 self, formatters: Optional[LiteralFormatters] = None 

1405 ) -> str: 

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

1407 :obj:`Variable`\\ s""" 

1408 

1409 return generate_c_value_initializer( 

1410 self.variable, formatters 

1411 ) + "".join( 

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

1413 for dim in self.subscript 

1414 ) 

1415 

1416 def __str__(self): 

1417 """Initialization string.""" 

1418 return self.init_target_code() 

1419 

1420 

1421class Dot(VariableValue): 

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

1423 

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

1425 for accessing a specific member of a struct. 

1426 

1427 Args: 

1428 variable: variable whose member we're accessing 

1429 member: name of member we're accessing 

1430 

1431 Attributes: 

1432 variable: variable whose member we're accessing 

1433 member: name of member we're accessing 

1434 

1435 Examples: 

1436 >>> from csnake import Variable, Dot 

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

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

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

1440 >>> print(var2) 

1441 int var2 = var1.some_member; 

1442 """ 

1443 

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

1445 

1446 def __init__( 

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

1448 ) -> None: 

1449 if not isinstance(variable, VariableValue): 

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

1451 self.variable = variable 

1452 

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

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

1455 self.member = member 

1456 

1457 def init_target_code( 

1458 self, formatters: Optional[LiteralFormatters] = None 

1459 ) -> str: 

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

1461 :obj:`Variable`\\ s""" 

1462 if isinstance(self.member, str): 

1463 return ( 

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

1465 ) 

1466 if isinstance(self.member, VariableValue): 

1467 return ( 

1468 self.variable.init_target_code(formatters) 

1469 + "." 

1470 + self.member.init_target_code(formatters) 

1471 ) 

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

1473 

1474 def __str__(self): 

1475 """Initialization string.""" 

1476 return self.init_target_code() 

1477 

1478 

1479class Arrow(VariableValue): 

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

1481 

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

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

1484 

1485 Args: 

1486 variable: variable whose member we're accessing 

1487 member: name of member we're accessing 

1488 

1489 Attributes: 

1490 variable: variable whose member we're accessing 

1491 member: name of member we're accessing 

1492 

1493 Examples: 

1494 >>> from csnake import Variable, Arrow 

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

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

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

1498 >>> print(var2) 

1499 int var2 = var1->some_member; 

1500 """ 

1501 

1502 __slots__ = "variable", "item" 

1503 

1504 def __init__( 

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

1506 ) -> None: 

1507 if not isinstance(variable, VariableValue): 

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

1509 self.variable = variable 

1510 

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

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

1513 self.item = item 

1514 

1515 def init_target_code( 

1516 self, formatters: Optional[LiteralFormatters] = None 

1517 ) -> str: 

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

1519 :obj:`Variable`\\ s""" 

1520 if isinstance(self.item, str): 

1521 return ( 

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

1523 ) 

1524 if isinstance(self.item, VariableValue): 

1525 return ( 

1526 self.variable.init_target_code(formatters) 

1527 + "->" 

1528 + self.item.init_target_code(formatters) 

1529 ) 

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

1531 

1532 def __str__(self): 

1533 """Initialization string.""" 

1534 return self.init_target_code() 

1535 

1536 

1537class GenericModifier(VariableValue): 

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

1539 

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

1541 should return a :obj:`str`. 

1542 

1543 Args: 

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

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

1546 

1547 Attributes: 

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

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

1550 

1551 Examples: 

1552 >>> from csnake import Variable, GenericModifier 

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

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

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

1556 >>> print(var2) 

1557 int var2 = TRANSLATE(var1); 

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

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

1560 >>> print(var3) 

1561 int var3 = do_whatever("test"); 

1562 """ 

1563 

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

1565 

1566 def __init__( 

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

1568 ) -> None: 

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

1570 raise TypeError( 

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

1572 ) 

1573 self.value = value 

1574 self.format_function = function 

1575 

1576 def init_target_code( 

1577 self, formatters: Optional[LiteralFormatters] = None 

1578 ) -> str: 

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

1580 :obj:`Variable`\\ s""" 

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

1582 

1583 def __str__(self): 

1584 """Initialization string.""" 

1585 return self.init_target_code() 

1586 

1587 

1588class OffsetOf(VariableValue): 

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

1590 members. 

1591 

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

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

1594 

1595 

1596 Args: 

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

1598 member: struct member whose offset we're using 

1599 

1600 Attributes: 

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

1602 member: struct member whose offset we're using 

1603 

1604 Examples: 

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

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

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

1608 >>> print(var2) 

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

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

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

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

1613 >>> print(var3) 

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

1615 """ 

1616 

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

1618 

1619 def __init__( 

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

1621 ) -> None: 

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

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

1624 self.struct = struct 

1625 

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

1627 raise TypeError( 

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

1629 ) 

1630 self.member = member 

1631 

1632 def init_target_code( 

1633 self, formatters: Optional[LiteralFormatters] = None 

1634 ) -> str: 

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

1636 :obj:`Variable`\\ s""" 

1637 if isinstance(self.struct, str): 

1638 struct_name = self.struct 

1639 elif isinstance(self.struct, Struct): 

1640 if self.struct.typedef: 

1641 struct_name = self.struct.name 

1642 else: 

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

1644 

1645 if isinstance(self.member, str): 

1646 member_name = self.member 

1647 elif isinstance(self.member, VariableValue): 

1648 member_name = self.member.init_target_code(formatters) 

1649 

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

1651 

1652 def __str__(self): 

1653 """Initialization string.""" 

1654 return self.init_target_code() 

1655 

1656 

1657class TextModifier(VariableValue): 

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

1659 :obj:`str`. 

1660 

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

1662 Initialization string. 

1663 

1664 Args: 

1665 text: initialization string 

1666 

1667 Attributes: 

1668 text: initialization string 

1669 

1670 Examples: 

1671 >>> from csnake import Variable, TextModifier 

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

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

1674 >>> print(var2) 

1675 int var2 = whatever + you + want; 

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

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

1678 >>> print(var3) 

1679 int var3 = you_can_do_this_too + var2; 

1680 """ 

1681 

1682 __slots__ = ("text",) 

1683 

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

1685 self.text = assure_str(text) 

1686 

1687 def init_target_code( 

1688 self, formatters: Optional[LiteralFormatters] = None 

1689 ) -> str: 

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

1691 :obj:`Variable`\\ s""" 

1692 return self.text 

1693 

1694 def __str__(self): 

1695 """Initialization string.""" 

1696 return self.init_target_code() 

1697 

1698 

1699class Function: 

1700 """Class describing C function. 

1701 

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

1703 call. 

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

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

1706 

1707 Args: 

1708 name: name of the function 

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

1710 

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

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

1713 

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

1715 

1716 * a :class:`Variable` 

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

1718 (`name`, `primitive`) 

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

1720 `primitive`) 

1721 

1722 

1723 Attributes: 

1724 name: name of the function 

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

1726 

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

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

1729 

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

1731 

1732 * a :class:`Variable` 

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

1734 (`name`, `primitive`) 

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

1736 `primitive`) 

1737 

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

1739 the funtion's body code. 

1740 """ 

1741 

1742 __slots__ = ( 

1743 "name", 

1744 "return_type", 

1745 "arguments", 

1746 "qualifiers", 

1747 "codewriter", 

1748 ) 

1749 

1750 def __init__( 

1751 self, 

1752 name: str, 

1753 return_type: str = "void", 

1754 qualifiers: Optional[Union[str, Iterable[str]]] = None, 

1755 arguments: Optional[Iterable] = None, 

1756 ) -> None: 

1757 self.name = assure_str(name) 

1758 self.return_type = assure_str(return_type) 

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

1760 self.codewriter = CodeWriterLite() 

1761 

1762 if isinstance(qualifiers, str): 

1763 self.qualifiers = qualifiers.split() 

1764 elif qualifiers is not None: 

1765 self.qualifiers = [ 

1766 assure_str(qualifier) for qualifier in qualifiers 

1767 ] 

1768 else: 

1769 self.qualifiers = [] 

1770 

1771 if arguments is not None: 

1772 for arg in arguments: 

1773 proc_arg = _get_variable(arg) 

1774 self.add_argument(proc_arg) 

1775 

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

1777 """Add an argument to function. 

1778 

1779 Arguments are added sequentially. 

1780 

1781 arg: an one the following types: 

1782 

1783 * a :class:`Variable` 

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

1785 (`name`, `primitive`) 

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

1787 `primitive`) 

1788 """ 

1789 

1790 proc_arg = _get_variable(arg) 

1791 

1792 self.arguments.append(proc_arg) 

1793 

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

1795 """Add multiple arguments to function. 

1796 

1797 Arguments are added sequentially. 

1798 

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

1800 

1801 * a :class:`Variable` 

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

1803 (`name`, `primitive`) 

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

1805 `primitive`) 

1806 """ 

1807 

1808 for arg in args: 

1809 self.add_argument(arg) 

1810 

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

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

1813 

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

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

1816 

1817 

1818 Args: 

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

1820 """ 

1821 

1822 self.codewriter.add_lines(code) 

1823 

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

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

1826 

1827 Args: 

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

1829 

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

1831 

1832 Examples: 

1833 >>> from csnake import Variable, Function 

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

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

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

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

1838 >>> fun = Function( 

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

1840 ... ) 

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

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

1843 testfunct(1, 2, 3, 4) 

1844 """ 

1845 

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

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

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

1849 

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

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

1852 

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

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

1855 

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

1857 """Generate function prototype code. 

1858 

1859 Args: 

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

1861 :ccode:`extern` 

1862 

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

1864 

1865 Examples: 

1866 >>> from csnake import Variable, Function 

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

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

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

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

1871 >>> fun = Function( 

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

1873 ... ) 

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

1875 >>> print(fun.generate_prototype()) 

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

1877 """ 

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

1879 extern="extern " if extern else "", 

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

1881 ret=self.return_type, 

1882 nm=self.name, 

1883 args=", ".join( 

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

1885 ) 

1886 if self.arguments 

1887 else "void", 

1888 ) 

1889 

1890 @property 

1891 def prototype(self) -> str: 

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

1893 

1894 Ends with a semicolon (;). 

1895 

1896 See Also: 

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

1898 """ 

1899 return self.generate_prototype() + ";" 

1900 

1901 def generate_definition( 

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

1903 ) -> CodeWriterLite: 

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

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

1906 

1907 Args: 

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

1909 spaces for indentation 

1910 

1911 Examples: 

1912 >>> from csnake import Variable, Function 

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

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

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

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

1917 >>> fun = Function( 

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

1919 ... ) 

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

1921 >>> print(fun.generate_definition()) 

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

1923 { 

1924 code; 

1925 more_code; 

1926 } 

1927 

1928 """ 

1929 writer = CodeWriterLite(indent=indent) 

1930 writer.add_line(self.generate_prototype()) 

1931 writer.open_brace() 

1932 

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

1934 

1935 writer.close_brace() 

1936 

1937 return writer 

1938 

1939 @property 

1940 def definition(self) -> str: 

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

1942 

1943 See Also: 

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

1945 """ 

1946 return self.generate_definition().code 

1947 

1948 def init_target_code( 

1949 self, formatters: Optional[LiteralFormatters] = None 

1950 ) -> str: 

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

1952 supplied formatters. 

1953 

1954 Args: 

1955 formatters: collection of formatters used for formatting the 

1956 initialization :obj:`str` 

1957 """ 

1958 return self.name 

1959 

1960 

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

1962 

1963VariableValue.register(Function) 

1964 

1965 

1966class Enum: 

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

1968 

1969 Args: 

1970 name: name of the enum 

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

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

1973 

1974 Attributes: 

1975 name: name of the enum 

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

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

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

1979 """ 

1980 

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

1982 

1983 class EnumValue: 

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

1985 

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

1987 

1988 def __init__( 

1989 self, 

1990 name: str, 

1991 value: Optional[Union[CLiteral, VariableValue]] = None, 

1992 comment: Optional[str] = None, 

1993 ) -> None: 

1994 self.name = assure_str(name) 

1995 if value is not None and not isinstance( 

1996 value, (CLiteral, VariableValue) 

1997 ): 

1998 raise ValueError( 

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

2000 ) 

2001 self.value = value 

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

2003 

2004 def __init__( 

2005 self, name: str, prefix: Optional[str] = None, typedef: bool = False 

2006 ) -> None: 

2007 self.typedef = bool(typedef) 

2008 # enum values 

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

2010 self.name = assure_str(name) 

2011 

2012 if prefix is not None: 

2013 self.prefix = assure_str(prefix) 

2014 else: 

2015 self.prefix = "" 

2016 

2017 def add_value( 

2018 self, 

2019 name: str, 

2020 value: Optional[Union[CLiteral, VariableValue]] = None, 

2021 comment: Optional[str] = None, 

2022 ) -> None: 

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

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

2025 

2026 def add_values( 

2027 self, 

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

2029 ) -> None: 

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

2031 

2032 Values can be one of: 

2033 

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

2035 * a collection of: 

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

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

2038 keys [`value`, `comment`] 

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

2040 `value`, `comment`] respectively 

2041 

2042 """ 

2043 if isinstance(values, Mapping): 

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

2045 self.add_value(name, value) 

2046 

2047 else: 

2048 for value in values: 

2049 if isinstance(value, str): 

2050 self.add_value(value) 

2051 elif isinstance(value, Mapping): 

2052 self.add_value(**value) 

2053 # lists and tuples 

2054 else: 

2055 defaults = (None, None, None) 

2056 name = seq_get(value, defaults, 0) 

2057 value_ = seq_get(value, defaults, 1) 

2058 comment = seq_get(value, defaults, 2) 

2059 

2060 name = assure_str(name) 

2061 

2062 self.add_value(name, value_, comment) 

2063 

2064 def generate_declaration( 

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

2066 ) -> CodeWriterLite: 

2067 """Generate enum declaration code. 

2068 

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

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

2071 

2072 Args: 

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

2074 spaces for indentation 

2075 

2076 

2077 Examples: 

2078 >>> from csnake import ( 

2079 ... Enum, Variable, Dereference, AddressOf 

2080 ... ) 

2081 >>> name = "somename" 

2082 >>> pfx = "pfx" 

2083 >>> typedef = False 

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

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

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

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

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

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

2090 >>> print(enum.generate_declaration()) 

2091 enum somename 

2092 { 

2093 pfxval1 = 1, 

2094 pfxval2 = *1000, 

2095 pfxval3 = varname, 

2096 pfxval4 = &varname /* some comment */ 

2097 }; 

2098 """ 

2099 

2100 gen = CodeWriterLite(indent=indent) 

2101 

2102 if self.typedef: 

2103 gen.add_line("typedef enum") 

2104 else: 

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

2106 gen.open_brace() 

2107 

2108 def _generate_lines( 

2109 values: Iterable, lastline: bool = False 

2110 ) -> Iterator: 

2111 return ( 

2112 ( 

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

2114 prefix=self.prefix, 

2115 name=assure_str(val.name), 

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

2117 if val.value is not None 

2118 else "", 

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

2120 ), 

2121 val.comment, 

2122 ) 

2123 for val in values 

2124 ) 

2125 

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

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

2128 

2129 for line in chain(commalines, lastline): 

2130 gen.add_line(*line) 

2131 

2132 gen.close_brace() 

2133 

2134 if self.typedef: 

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

2136 else: 

2137 gen.add(";") 

2138 

2139 return gen 

2140 

2141 @property 

2142 def declaration(self) -> str: 

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

2144 

2145 

2146 See Also: 

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

2148 """ 

2149 return self.generate_declaration().code 

2150 

2151 def __str__(self) -> str: 

2152 """ 

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

2154 """ 

2155 return self.declaration