number.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. """Immutable number vars."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import functools
  5. import json
  6. import math
  7. from typing import (
  8. TYPE_CHECKING,
  9. Any,
  10. Callable,
  11. NoReturn,
  12. Sequence,
  13. TypeVar,
  14. Union,
  15. cast,
  16. overload,
  17. )
  18. from typing_extensions import Unpack
  19. from reflex.constants.base import Dirs
  20. from reflex.utils.exceptions import PrimitiveUnserializableToJSONError, VarTypeError
  21. from reflex.utils.imports import ImportDict, ImportVar
  22. from .base import (
  23. VAR_TYPE,
  24. CachedVarOperation,
  25. CustomVarOperationReturn,
  26. LiteralVar,
  27. ReflexCallable,
  28. Var,
  29. VarData,
  30. cached_property_no_lock,
  31. nary_type_computer,
  32. passthrough_unary_type_computer,
  33. unionize,
  34. var_operation,
  35. var_operation_return,
  36. )
  37. NUMBER_T = TypeVar("NUMBER_T", int, float, bool)
  38. if TYPE_CHECKING:
  39. from .function import FunctionVar
  40. from .sequence import ArrayVar
  41. def raise_unsupported_operand_types(
  42. operator: str, operands_types: tuple[type, ...]
  43. ) -> NoReturn:
  44. """Raise an unsupported operand types error.
  45. Args:
  46. operator: The operator.
  47. operands_types: The types of the operands.
  48. Raises:
  49. VarTypeError: The operand types are unsupported.
  50. """
  51. raise VarTypeError(
  52. f"Unsupported Operand type(s) for {operator}: {', '.join(t.__name__ for t in operands_types)}"
  53. )
  54. class NumberVar(Var[NUMBER_T], python_types=(int, float)):
  55. """Base class for immutable number vars."""
  56. def __add__(self, other: number_types) -> NumberVar:
  57. """Add two numbers.
  58. Args:
  59. other: The other number.
  60. Returns:
  61. The number addition operation.
  62. """
  63. if not isinstance(other, NUMBER_TYPES):
  64. raise_unsupported_operand_types("+", (type(self), type(other)))
  65. return number_add_operation(self, +other).guess_type()
  66. def __radd__(self, other: number_types) -> NumberVar:
  67. """Add two numbers.
  68. Args:
  69. other: The other number.
  70. Returns:
  71. The number addition operation.
  72. """
  73. if not isinstance(other, NUMBER_TYPES):
  74. raise_unsupported_operand_types("+", (type(other), type(self)))
  75. return number_add_operation(+other, self).guess_type()
  76. def __sub__(self, other: number_types) -> NumberVar:
  77. """Subtract two numbers.
  78. Args:
  79. other: The other number.
  80. Returns:
  81. The number subtraction operation.
  82. """
  83. if not isinstance(other, NUMBER_TYPES):
  84. raise_unsupported_operand_types("-", (type(self), type(other)))
  85. return number_subtract_operation(self, +other).guess_type()
  86. def __rsub__(self, other: number_types) -> NumberVar:
  87. """Subtract two numbers.
  88. Args:
  89. other: The other number.
  90. Returns:
  91. The number subtraction operation.
  92. """
  93. if not isinstance(other, NUMBER_TYPES):
  94. raise_unsupported_operand_types("-", (type(other), type(self)))
  95. return number_subtract_operation(+other, self).guess_type()
  96. def __abs__(self):
  97. """Get the absolute value of the number.
  98. Returns:
  99. The number absolute operation.
  100. """
  101. return number_abs_operation(self)
  102. @overload
  103. def __mul__(self, other: number_types | boolean_types) -> NumberVar: ...
  104. @overload
  105. def __mul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  106. def __mul__(self, other: Any):
  107. """Multiply two numbers.
  108. Args:
  109. other: The other number.
  110. Returns:
  111. The number multiplication operation.
  112. """
  113. from .sequence import ArrayVar, LiteralArrayVar
  114. if isinstance(other, (list, tuple, ArrayVar)):
  115. if isinstance(other, ArrayVar):
  116. return other * self
  117. return LiteralArrayVar.create(other) * self
  118. if not isinstance(other, NUMBER_TYPES):
  119. raise_unsupported_operand_types("*", (type(self), type(other)))
  120. return number_multiply_operation(self, +other).guess_type()
  121. @overload
  122. def __rmul__(self, other: number_types | boolean_types) -> NumberVar: ...
  123. @overload
  124. def __rmul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ...
  125. def __rmul__(self, other: Any):
  126. """Multiply two numbers.
  127. Args:
  128. other: The other number.
  129. Returns:
  130. The number multiplication operation.
  131. """
  132. from .sequence import ArrayVar, LiteralArrayVar
  133. if isinstance(other, (list, tuple, ArrayVar)):
  134. if isinstance(other, ArrayVar):
  135. return other * self
  136. return LiteralArrayVar.create(other) * self
  137. if not isinstance(other, NUMBER_TYPES):
  138. raise_unsupported_operand_types("*", (type(other), type(self)))
  139. return number_multiply_operation(+other, self).guess_type()
  140. def __truediv__(self, other: number_types) -> NumberVar:
  141. """Divide two numbers.
  142. Args:
  143. other: The other number.
  144. Returns:
  145. The number true division operation.
  146. """
  147. if not isinstance(other, NUMBER_TYPES):
  148. raise_unsupported_operand_types("/", (type(self), type(other)))
  149. return number_true_division_operation(self, +other).guess_type()
  150. def __rtruediv__(self, other: number_types) -> NumberVar:
  151. """Divide two numbers.
  152. Args:
  153. other: The other number.
  154. Returns:
  155. The number true division operation.
  156. """
  157. if not isinstance(other, NUMBER_TYPES):
  158. raise_unsupported_operand_types("/", (type(other), type(self)))
  159. return number_true_division_operation(+other, self).guess_type()
  160. def __floordiv__(self, other: number_types) -> NumberVar:
  161. """Floor divide two numbers.
  162. Args:
  163. other: The other number.
  164. Returns:
  165. The number floor division operation.
  166. """
  167. if not isinstance(other, NUMBER_TYPES):
  168. raise_unsupported_operand_types("//", (type(self), type(other)))
  169. return number_floor_division_operation(self, +other).guess_type()
  170. def __rfloordiv__(self, other: number_types) -> NumberVar:
  171. """Floor divide two numbers.
  172. Args:
  173. other: The other number.
  174. Returns:
  175. The number floor division operation.
  176. """
  177. if not isinstance(other, NUMBER_TYPES):
  178. raise_unsupported_operand_types("//", (type(other), type(self)))
  179. return number_floor_division_operation(+other, self).guess_type()
  180. def __mod__(self, other: number_types) -> NumberVar:
  181. """Modulo two numbers.
  182. Args:
  183. other: The other number.
  184. Returns:
  185. The number modulo operation.
  186. """
  187. if not isinstance(other, NUMBER_TYPES):
  188. raise_unsupported_operand_types("%", (type(self), type(other)))
  189. return number_modulo_operation(self, +other).guess_type()
  190. def __rmod__(self, other: number_types) -> NumberVar:
  191. """Modulo two numbers.
  192. Args:
  193. other: The other number.
  194. Returns:
  195. The number modulo operation.
  196. """
  197. if not isinstance(other, NUMBER_TYPES):
  198. raise_unsupported_operand_types("%", (type(other), type(self)))
  199. return number_modulo_operation(+other, self).guess_type()
  200. def __pow__(self, other: number_types) -> NumberVar:
  201. """Exponentiate two numbers.
  202. Args:
  203. other: The other number.
  204. Returns:
  205. The number exponent operation.
  206. """
  207. if not isinstance(other, NUMBER_TYPES):
  208. raise_unsupported_operand_types("**", (type(self), type(other)))
  209. return number_exponent_operation(self, +other).guess_type()
  210. def __rpow__(self, other: number_types) -> NumberVar:
  211. """Exponentiate two numbers.
  212. Args:
  213. other: The other number.
  214. Returns:
  215. The number exponent operation.
  216. """
  217. if not isinstance(other, NUMBER_TYPES):
  218. raise_unsupported_operand_types("**", (type(other), type(self)))
  219. return number_exponent_operation(+other, self).guess_type()
  220. def __neg__(self):
  221. """Negate the number.
  222. Returns:
  223. The number negation operation.
  224. """
  225. return number_negate_operation(self).guess_type()
  226. def __invert__(self):
  227. """Boolean NOT the number.
  228. Returns:
  229. The boolean NOT operation.
  230. """
  231. return boolean_not_operation(self.bool()).guess_type()
  232. def __pos__(self) -> NumberVar:
  233. """Positive the number.
  234. Returns:
  235. The number.
  236. """
  237. return self
  238. def __round__(self):
  239. """Round the number.
  240. Returns:
  241. The number round operation.
  242. """
  243. return number_round_operation(self).guess_type()
  244. def __ceil__(self):
  245. """Ceil the number.
  246. Returns:
  247. The number ceil operation.
  248. """
  249. return number_ceil_operation(self).guess_type()
  250. def __floor__(self):
  251. """Floor the number.
  252. Returns:
  253. The number floor operation.
  254. """
  255. return number_floor_operation(self).guess_type()
  256. def __trunc__(self):
  257. """Trunc the number.
  258. Returns:
  259. The number trunc operation.
  260. """
  261. return number_trunc_operation(self).guess_type()
  262. def __lt__(self, other: number_types) -> BooleanVar:
  263. """Less than comparison.
  264. Args:
  265. other: The other number.
  266. Returns:
  267. The result of the comparison.
  268. """
  269. if not isinstance(other, NUMBER_TYPES):
  270. raise_unsupported_operand_types("<", (type(self), type(other)))
  271. return less_than_operation(+self, +other).guess_type()
  272. def __le__(self, other: number_types) -> BooleanVar:
  273. """Less than or equal comparison.
  274. Args:
  275. other: The other number.
  276. Returns:
  277. The result of the comparison.
  278. """
  279. if not isinstance(other, NUMBER_TYPES):
  280. raise_unsupported_operand_types("<=", (type(self), type(other)))
  281. return less_than_or_equal_operation(+self, +other).guess_type()
  282. def __eq__(self, other: Any) -> BooleanVar:
  283. """Equal comparison.
  284. Args:
  285. other: The other number.
  286. Returns:
  287. The result of the comparison.
  288. """
  289. if isinstance(other, NUMBER_TYPES):
  290. return equal_operation(+self, +other).guess_type()
  291. return equal_operation(self, other).guess_type()
  292. def __ne__(self, other: Any) -> BooleanVar:
  293. """Not equal comparison.
  294. Args:
  295. other: The other number.
  296. Returns:
  297. The result of the comparison.
  298. """
  299. if isinstance(other, NUMBER_TYPES):
  300. return not_equal_operation(+self, +other).guess_type()
  301. return not_equal_operation(self, other).guess_type()
  302. def __gt__(self, other: number_types) -> BooleanVar:
  303. """Greater than comparison.
  304. Args:
  305. other: The other number.
  306. Returns:
  307. The result of the comparison.
  308. """
  309. if not isinstance(other, NUMBER_TYPES):
  310. raise_unsupported_operand_types(">", (type(self), type(other)))
  311. return greater_than_operation(+self, +other).guess_type()
  312. def __ge__(self, other: number_types) -> BooleanVar:
  313. """Greater than or equal comparison.
  314. Args:
  315. other: The other number.
  316. Returns:
  317. The result of the comparison.
  318. """
  319. if not isinstance(other, NUMBER_TYPES):
  320. raise_unsupported_operand_types(">=", (type(self), type(other)))
  321. return greater_than_or_equal_operation(+self, +other).guess_type()
  322. def _is_strict_float(self) -> bool:
  323. """Check if the number is a float.
  324. Returns:
  325. bool: True if the number is a float.
  326. """
  327. return issubclass(self._var_type, float)
  328. def _is_strict_int(self) -> bool:
  329. """Check if the number is an int.
  330. Returns:
  331. bool: True if the number is an int.
  332. """
  333. return issubclass(self._var_type, int)
  334. def binary_number_operation(
  335. func: Callable[[Var[int | float], Var[int | float]], str],
  336. ):
  337. """Decorator to create a binary number operation.
  338. Args:
  339. func: The binary number operation function.
  340. Returns:
  341. The binary number operation.
  342. """
  343. def operation(
  344. lhs: Var[int | float], rhs: Var[int | float]
  345. ) -> CustomVarOperationReturn[int | float]:
  346. def type_computer(*args: Var):
  347. if not args:
  348. return (
  349. ReflexCallable[[int | float, int | float], int | float],
  350. type_computer,
  351. )
  352. if len(args) == 1:
  353. return (
  354. ReflexCallable[[int | float], int | float],
  355. functools.partial(type_computer, args[0]),
  356. )
  357. return (
  358. ReflexCallable[[], unionize(args[0]._var_type, args[1]._var_type)],
  359. None,
  360. )
  361. return var_operation_return(
  362. js_expression=func(lhs, rhs),
  363. type_computer=type_computer,
  364. )
  365. operation.__name__ = func.__name__
  366. return var_operation(operation)
  367. @binary_number_operation
  368. def number_add_operation(lhs: Var[int | float], rhs: Var[int | float]):
  369. """Add two numbers.
  370. Args:
  371. lhs: The first number.
  372. rhs: The second number.
  373. Returns:
  374. The number addition operation.
  375. """
  376. return f"({lhs} + {rhs})"
  377. @binary_number_operation
  378. def number_subtract_operation(lhs: Var[int | float], rhs: Var[int | float]):
  379. """Subtract two numbers.
  380. Args:
  381. lhs: The first number.
  382. rhs: The second number.
  383. Returns:
  384. The number subtraction operation.
  385. """
  386. return f"({lhs} - {rhs})"
  387. unary_operation_type_computer = passthrough_unary_type_computer(
  388. ReflexCallable[[int | float], int | float]
  389. )
  390. @var_operation
  391. def number_abs_operation(
  392. value: Var[int | float],
  393. ) -> CustomVarOperationReturn[int | float]:
  394. """Get the absolute value of the number.
  395. Args:
  396. value: The number.
  397. Returns:
  398. The number absolute operation.
  399. """
  400. return var_operation_return(
  401. js_expression=f"Math.abs({value})",
  402. type_computer=unary_operation_type_computer,
  403. _raw_js_function="Math.abs",
  404. )
  405. @binary_number_operation
  406. def number_multiply_operation(lhs: Var[int | float], rhs: Var[int | float]):
  407. """Multiply two numbers.
  408. Args:
  409. lhs: The first number.
  410. rhs: The second number.
  411. Returns:
  412. The number multiplication operation.
  413. """
  414. return f"({lhs} * {rhs})"
  415. @var_operation
  416. def number_negate_operation(
  417. value: Var[NUMBER_T],
  418. ) -> CustomVarOperationReturn[NUMBER_T]:
  419. """Negate the number.
  420. Args:
  421. value: The number.
  422. Returns:
  423. The number negation operation.
  424. """
  425. return var_operation_return(
  426. js_expression=f"-({value})", type_computer=unary_operation_type_computer
  427. )
  428. @binary_number_operation
  429. def number_true_division_operation(lhs: Var[int | float], rhs: Var[int | float]):
  430. """Divide two numbers.
  431. Args:
  432. lhs: The first number.
  433. rhs: The second number.
  434. Returns:
  435. The number true division operation.
  436. """
  437. return f"({lhs} / {rhs})"
  438. @binary_number_operation
  439. def number_floor_division_operation(lhs: Var[int | float], rhs: Var[int | float]):
  440. """Floor divide two numbers.
  441. Args:
  442. lhs: The first number.
  443. rhs: The second number.
  444. Returns:
  445. The number floor division operation.
  446. """
  447. return f"Math.floor({lhs} / {rhs})"
  448. @binary_number_operation
  449. def number_modulo_operation(lhs: Var[int | float], rhs: Var[int | float]):
  450. """Modulo two numbers.
  451. Args:
  452. lhs: The first number.
  453. rhs: The second number.
  454. Returns:
  455. The number modulo operation.
  456. """
  457. return f"({lhs} % {rhs})"
  458. @binary_number_operation
  459. def number_exponent_operation(lhs: Var[int | float], rhs: Var[int | float]):
  460. """Exponentiate two numbers.
  461. Args:
  462. lhs: The first number.
  463. rhs: The second number.
  464. Returns:
  465. The number exponent operation.
  466. """
  467. return f"({lhs} ** {rhs})"
  468. @var_operation
  469. def number_round_operation(value: Var[int | float]):
  470. """Round the number.
  471. Args:
  472. value: The number.
  473. Returns:
  474. The number round operation.
  475. """
  476. return var_operation_return(js_expression=f"Math.round({value})", var_type=int)
  477. @var_operation
  478. def number_ceil_operation(value: Var[int | float]):
  479. """Ceil the number.
  480. Args:
  481. value: The number.
  482. Returns:
  483. The number ceil operation.
  484. """
  485. return var_operation_return(js_expression=f"Math.ceil({value})", var_type=int)
  486. @var_operation
  487. def number_floor_operation(value: Var[int | float]):
  488. """Floor the number.
  489. Args:
  490. value: The number.
  491. Returns:
  492. The number floor operation.
  493. """
  494. return var_operation_return(
  495. js_expression=f"Math.floor({value})",
  496. var_type=int,
  497. _raw_js_function="Math.floor",
  498. )
  499. @var_operation
  500. def number_trunc_operation(value: Var[int | float]):
  501. """Trunc the number.
  502. Args:
  503. value: The number.
  504. Returns:
  505. The number trunc operation.
  506. """
  507. return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int)
  508. class BooleanVar(NumberVar[bool], python_types=bool):
  509. """Base class for immutable boolean vars."""
  510. def __invert__(self):
  511. """NOT the boolean.
  512. Returns:
  513. The boolean NOT operation.
  514. """
  515. return boolean_not_operation(self).guess_type()
  516. def __int__(self):
  517. """Convert the boolean to an int.
  518. Returns:
  519. The boolean to int operation.
  520. """
  521. return boolean_to_number_operation(self).guess_type()
  522. def __pos__(self):
  523. """Convert the boolean to an int.
  524. Returns:
  525. The boolean to int operation.
  526. """
  527. return boolean_to_number_operation(self).guess_type()
  528. def bool(self) -> BooleanVar:
  529. """Boolean conversion.
  530. Returns:
  531. The boolean value of the boolean.
  532. """
  533. return self
  534. def __lt__(self, other: Any):
  535. """Less than comparison.
  536. Args:
  537. other: The other boolean.
  538. Returns:
  539. The result of the comparison.
  540. """
  541. return +self < other
  542. def __le__(self, other: Any):
  543. """Less than or equal comparison.
  544. Args:
  545. other: The other boolean.
  546. Returns:
  547. The result of the comparison.
  548. """
  549. return +self <= other
  550. def __gt__(self, other: Any):
  551. """Greater than comparison.
  552. Args:
  553. other: The other boolean.
  554. Returns:
  555. The result of the comparison.
  556. """
  557. return +self > other
  558. def __ge__(self, other: Any):
  559. """Greater than or equal comparison.
  560. Args:
  561. other: The other boolean.
  562. Returns:
  563. The result of the comparison.
  564. """
  565. return +self >= other
  566. @var_operation
  567. def boolean_to_number_operation(value: Var[bool]):
  568. """Convert the boolean to a number.
  569. Args:
  570. value: The boolean.
  571. Returns:
  572. The boolean to number operation.
  573. """
  574. return var_operation_return(
  575. js_expression=f"Number({value})", var_type=int, _raw_js_function="Number"
  576. )
  577. def comparison_operator(
  578. func: Callable[[Var, Var], str],
  579. ) -> FunctionVar[ReflexCallable[[Any, Any], bool]]:
  580. """Decorator to create a comparison operation.
  581. Args:
  582. func: The comparison operation function.
  583. Returns:
  584. The comparison operation.
  585. """
  586. def operation(lhs: Var[Any], rhs: Var[Any]):
  587. return var_operation_return(
  588. js_expression=func(lhs, rhs),
  589. var_type=bool,
  590. )
  591. operation.__name__ = func.__name__
  592. return var_operation(operation)
  593. @comparison_operator
  594. def greater_than_operation(lhs: Var, rhs: Var):
  595. """Greater than comparison.
  596. Args:
  597. lhs: The first value.
  598. rhs: The second value.
  599. Returns:
  600. The result of the comparison.
  601. """
  602. return f"({lhs} > {rhs})"
  603. @comparison_operator
  604. def greater_than_or_equal_operation(lhs: Var, rhs: Var):
  605. """Greater than or equal comparison.
  606. Args:
  607. lhs: The first value.
  608. rhs: The second value.
  609. Returns:
  610. The result of the comparison.
  611. """
  612. return f"({lhs} >= {rhs})"
  613. @comparison_operator
  614. def less_than_operation(lhs: Var, rhs: Var):
  615. """Less than comparison.
  616. Args:
  617. lhs: The first value.
  618. rhs: The second value.
  619. Returns:
  620. The result of the comparison.
  621. """
  622. return f"({lhs} < {rhs})"
  623. @comparison_operator
  624. def less_than_or_equal_operation(lhs: Var, rhs: Var):
  625. """Less than or equal comparison.
  626. Args:
  627. lhs: The first value.
  628. rhs: The second value.
  629. Returns:
  630. The result of the comparison.
  631. """
  632. return f"({lhs} <= {rhs})"
  633. @comparison_operator
  634. def equal_operation(lhs: Var, rhs: Var):
  635. """Equal comparison.
  636. Args:
  637. lhs: The first value.
  638. rhs: The second value.
  639. Returns:
  640. The result of the comparison.
  641. """
  642. return f"({lhs} === {rhs})"
  643. @comparison_operator
  644. def not_equal_operation(lhs: Var, rhs: Var):
  645. """Not equal comparison.
  646. Args:
  647. lhs: The first value.
  648. rhs: The second value.
  649. Returns:
  650. The result of the comparison.
  651. """
  652. return f"({lhs} !== {rhs})"
  653. @var_operation
  654. def boolean_not_operation(value: Var[bool]):
  655. """Boolean NOT the boolean.
  656. Args:
  657. value: The boolean.
  658. Returns:
  659. The boolean NOT operation.
  660. """
  661. return var_operation_return(js_expression=f"!({value})", var_type=bool)
  662. @dataclasses.dataclass(
  663. eq=False,
  664. frozen=True,
  665. slots=True,
  666. )
  667. class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]):
  668. """Base class for immutable literal number vars."""
  669. _var_value: float | int = dataclasses.field(default=0)
  670. def json(self) -> str:
  671. """Get the JSON representation of the var.
  672. Returns:
  673. The JSON representation of the var.
  674. Raises:
  675. PrimitiveUnserializableToJSONError: If the var is unserializable to JSON.
  676. """
  677. if math.isinf(self._var_value) or math.isnan(self._var_value):
  678. raise PrimitiveUnserializableToJSONError(
  679. f"No valid JSON representation for {self}"
  680. )
  681. return json.dumps(self._var_value)
  682. def __hash__(self) -> int:
  683. """Calculate the hash value of the object.
  684. Returns:
  685. int: The hash value of the object.
  686. """
  687. return hash((type(self).__name__, self._var_value))
  688. @classmethod
  689. def create(cls, value: float | int, _var_data: VarData | None = None):
  690. """Create the number var.
  691. Args:
  692. value: The value of the var.
  693. _var_data: Additional hooks and imports associated with the Var.
  694. Returns:
  695. The number var.
  696. """
  697. if math.isinf(value):
  698. js_expr = "Infinity" if value > 0 else "-Infinity"
  699. elif math.isnan(value):
  700. js_expr = "NaN"
  701. else:
  702. js_expr = str(value)
  703. return cls(
  704. _js_expr=js_expr,
  705. _var_type=type(value),
  706. _var_data=_var_data,
  707. _var_value=value,
  708. )
  709. @dataclasses.dataclass(
  710. eq=False,
  711. frozen=True,
  712. slots=True,
  713. )
  714. class LiteralBooleanVar(LiteralVar, BooleanVar):
  715. """Base class for immutable literal boolean vars."""
  716. _var_value: bool = dataclasses.field(default=False)
  717. def json(self) -> str:
  718. """Get the JSON representation of the var.
  719. Returns:
  720. The JSON representation of the var.
  721. """
  722. return "true" if self._var_value else "false"
  723. def __hash__(self) -> int:
  724. """Calculate the hash value of the object.
  725. Returns:
  726. int: The hash value of the object.
  727. """
  728. return hash((type(self).__name__, self._var_value))
  729. @classmethod
  730. def create(cls, value: bool, _var_data: VarData | None = None):
  731. """Create the boolean var.
  732. Args:
  733. value: The value of the var.
  734. _var_data: Additional hooks and imports associated with the Var.
  735. Returns:
  736. The boolean var.
  737. """
  738. return cls(
  739. _js_expr="true" if value else "false",
  740. _var_type=bool,
  741. _var_data=_var_data,
  742. _var_value=value,
  743. )
  744. number_types = Union[NumberVar, int, float]
  745. boolean_types = Union[BooleanVar, bool]
  746. _IS_TRUE_IMPORT: ImportDict = {
  747. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
  748. }
  749. _AT_SLICE_IMPORT: ImportDict = {
  750. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSlice")],
  751. }
  752. _AT_SLICE_OR_INDEX: ImportDict = {
  753. f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSliceOrIndex")],
  754. }
  755. _RANGE_IMPORT: ImportDict = {
  756. f"$/{Dirs.UTILS}/helpers/range": [ImportVar(tag="range", is_default=True)],
  757. }
  758. @var_operation
  759. def boolify(value: Var):
  760. """Convert the value to a boolean.
  761. Args:
  762. value: The value.
  763. Returns:
  764. The boolean value.
  765. """
  766. return var_operation_return(
  767. js_expression=f"isTrue({value})",
  768. var_type=bool,
  769. var_data=VarData(imports=_IS_TRUE_IMPORT),
  770. _raw_js_function="isTrue",
  771. )
  772. T = TypeVar("T", bound=Any)
  773. U = TypeVar("U", bound=Any)
  774. @var_operation
  775. def ternary_operation(
  776. condition: Var[bool], if_true: Var[T], if_false: Var[U]
  777. ) -> CustomVarOperationReturn[Union[T, U]]:
  778. """Create a ternary operation.
  779. Args:
  780. condition: The condition.
  781. if_true: The value if the condition is true.
  782. if_false: The value if the condition is false.
  783. Returns:
  784. The ternary operation.
  785. """
  786. value: CustomVarOperationReturn[Union[T, U]] = var_operation_return(
  787. js_expression=f"({condition} ? {if_true} : {if_false})",
  788. type_computer=nary_type_computer(
  789. ReflexCallable[[bool, Any, Any], Any],
  790. ReflexCallable[[Any, Any], Any],
  791. ReflexCallable[[Any], Any],
  792. computer=lambda args: unionize(args[1]._var_type, args[2]._var_type),
  793. ),
  794. )
  795. return value
  796. TUPLE_ENDS_IN_VAR = tuple[Unpack[tuple[Var[Any], ...]], Var[VAR_TYPE]]
  797. TUPLE_ENDS_IN_VAR_RELAXED = tuple[
  798. Unpack[tuple[Var[Any] | Any, ...]], Var[VAR_TYPE] | VAR_TYPE
  799. ]
  800. @dataclasses.dataclass(
  801. eq=False,
  802. frozen=True,
  803. slots=True,
  804. )
  805. class MatchOperation(CachedVarOperation, Var[VAR_TYPE]):
  806. """Base class for immutable match operations."""
  807. _cond: Var[bool] = dataclasses.field(
  808. default_factory=lambda: LiteralBooleanVar.create(True)
  809. )
  810. _cases: tuple[TUPLE_ENDS_IN_VAR[VAR_TYPE], ...] = dataclasses.field(
  811. default_factory=tuple
  812. )
  813. _default: Var[VAR_TYPE] = dataclasses.field( # pyright: ignore[reportAssignmentType]
  814. default_factory=lambda: Var.create(None)
  815. )
  816. @cached_property_no_lock
  817. def _cached_var_name(self) -> str:
  818. """Get the name of the var.
  819. Returns:
  820. The name of the var.
  821. """
  822. switch_code = f"(() => {{ switch (JSON.stringify({self._cond!s})) {{"
  823. for case in self._cases:
  824. conditions = case[:-1]
  825. return_value = case[-1]
  826. case_conditions = " ".join(
  827. [f"case JSON.stringify({condition!s}):" for condition in conditions]
  828. )
  829. case_code = f"{case_conditions} return ({return_value!s}); break;"
  830. switch_code += case_code
  831. switch_code += f"default: return ({self._default!s}); break;"
  832. switch_code += "};})()"
  833. return switch_code
  834. @cached_property_no_lock
  835. def _cached_get_all_var_data(self) -> VarData | None:
  836. """Get the VarData for the var.
  837. Returns:
  838. The VarData for the var.
  839. """
  840. return VarData.merge(
  841. self._cond._get_all_var_data(),
  842. *(
  843. cond_or_return._get_all_var_data()
  844. for case in self._cases
  845. for cond_or_return in case
  846. ),
  847. self._default._get_all_var_data(),
  848. self._var_data,
  849. )
  850. @classmethod
  851. def create(
  852. cls,
  853. cond: Any,
  854. cases: Sequence[TUPLE_ENDS_IN_VAR_RELAXED[VAR_TYPE]],
  855. default: Var[VAR_TYPE] | VAR_TYPE,
  856. _var_data: VarData | None = None,
  857. _var_type: type[VAR_TYPE] | None = None,
  858. ):
  859. """Create the match operation.
  860. Args:
  861. cond: The condition.
  862. cases: The cases.
  863. default: The default case.
  864. _var_data: Additional hooks and imports associated with the Var.
  865. _var_type: The type of the Var.
  866. Returns:
  867. The match operation.
  868. """
  869. cond = Var.create(cond)
  870. cases = cast(
  871. tuple[TUPLE_ENDS_IN_VAR[VAR_TYPE], ...],
  872. tuple(tuple(Var.create(c) for c in case) for case in cases),
  873. )
  874. _default = cast(Var[VAR_TYPE], Var.create(default))
  875. var_type = _var_type or unionize(
  876. *(case[-1]._var_type for case in cases),
  877. _default._var_type,
  878. )
  879. return cls(
  880. _js_expr="",
  881. _var_data=_var_data,
  882. _var_type=var_type,
  883. _cond=cond,
  884. _cases=cases,
  885. _default=_default,
  886. )
  887. NUMBER_TYPES = (int, float, NumberVar)