number.py 29 KB

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