curryparty 0.2.0__py3-none-any.whl → 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
curryparty/__init__.py CHANGED
@@ -6,10 +6,13 @@ except ImportError:
6
6
  raise ImportError(
7
7
  "curryparty needs the `polars` library. \n Please install it, typically with `pip install polars`"
8
8
  )
9
- from svg import SVG
9
+ import uuid
10
+
11
+ from svg import SVG, Rect
10
12
 
11
13
  from .core import SCHEMA, beta_reduce, compose, find_redexes, find_variables, subtree
12
14
  from .display import (
15
+ compute_height,
13
16
  compute_svg_frame_final,
14
17
  compute_svg_frame_init,
15
18
  compute_svg_frame_phase_a,
@@ -55,23 +58,29 @@ class Term:
55
58
  if term is None:
56
59
  break
57
60
 
58
- def show_beta(self):
61
+ def show_beta(self, x0=-10, width=30):
62
+ """
63
+ Generates an HTML representation that toggles visibility between
64
+ a static state and a SMIL animation on hover using pure CSS.
65
+ """
59
66
  candidates = find_redexes(self.nodes)
60
67
  if len(candidates) == 0:
61
- return None
68
+ return self._repr_html_()
69
+
62
70
  _redex, lamb, b = candidates.row(0)
63
71
  new_nodes = beta_reduce(self.nodes, lamb, b)
64
72
  vars = find_variables(self.nodes, lamb)["id"]
65
73
  b_subtree = subtree(self.nodes, b)
74
+ height = compute_height(self.nodes) + 3
66
75
  shapes: dict[int, ShapeAnim] = {}
67
- N_STEPS = 8
76
+ N_STEPS = 6
68
77
 
69
78
  for t in range(N_STEPS):
70
- if t < 2:
79
+ if t == 0:
71
80
  items = compute_svg_frame_init(self.nodes)
72
- elif t == 2 or t == 3:
81
+ elif t == 1 or t == 2:
73
82
  items = compute_svg_frame_phase_a(self.nodes, lamb, b_subtree, vars)
74
- elif t == 4 or t == 5:
83
+ elif t == 3 or t == 4:
75
84
  items = compute_svg_frame_phase_b(
76
85
  self.nodes, lamb, b_subtree, new_nodes
77
86
  )
@@ -82,33 +91,58 @@ class Term:
82
91
  shapes[k] = ShapeAnim(e)
83
92
  shapes[k].append_frame(t, attributes.items())
84
93
 
85
- elements = [x.to_element(N_STEPS) for x in shapes.values()]
86
- height = 2 + int(0.6 * len(self.nodes))
94
+ figure_id = uuid.uuid4()
95
+ box_id = f"lambda_box_{figure_id}".replace("-", "")
96
+ anim_elements = [
97
+ x.to_element(N_STEPS, begin=f"{box_id}.click", reset=f"{box_id}.mouseover")
98
+ for x in shapes.values()
99
+ ]
100
+
101
+ anim_elements.append(
102
+ Rect(
103
+ id=box_id,
104
+ x=str(x0),
105
+ y="0",
106
+ width="100%",
107
+ height="100%",
108
+ fill="transparent",
109
+ )
110
+ )
111
+
112
+ anim_svg = SVG(
113
+ xmlns="http://www.w3.org/2000/svg",
114
+ viewBox=f"{x0} 0 {width} {height}",
115
+ height=f"{35 * height}px",
116
+ elements=anim_elements,
117
+ ).as_str()
118
+
87
119
  return Html(
88
- SVG(
89
- xmlns="http://www.w3.org/2000/svg",
90
- viewBox=f"-10 0 30 {height}", # type: ignore
91
- height=f"{35 * height}px", # type: ignore
92
- elements=elements,
93
- ).as_str()
120
+ f'<div><div style="margin:5px">click to animate, move away and back to reset</div>{anim_svg}</div>'
94
121
  )
95
122
 
96
- def _repr_html_(self):
123
+ def _repr_html_(self, x0=-10, width=30):
97
124
  frame = compute_svg_frame_init(self.nodes)
98
125
  elements = []
99
- height = 2 + int(0.6 * len(self.nodes))
126
+
127
+ height = compute_height(self.nodes) + 1
100
128
  for _, e, attributes in frame:
101
129
  for name, v in attributes.items():
102
130
  e.__setattr__(name, v)
103
131
  elements.append(e)
104
132
  return SVG(
105
133
  xmlns="http://www.w3.org/2000/svg",
106
- viewBox=f"-10 0 30 {height}", # type: ignore
134
+ viewBox=f"{x0} 0 {width} {height}", # type: ignore
107
135
  height=f"{35 * height}px", # type: ignore
108
136
  elements=elements,
109
137
  ).as_str()
110
138
 
111
139
 
140
+ def offset_var(x: Union[int, str], offset: int) -> Union[int, str]:
141
+ if isinstance(x, int):
142
+ return x + offset
143
+ return x
144
+
145
+
112
146
  class L:
113
147
  def __init__(self, *lambda_names):
114
148
  self.n = len(lambda_names)
@@ -126,7 +160,7 @@ class L:
126
160
  if isinstance(t, L):
127
161
  offset = self.n
128
162
  for i, x in t.refs:
129
- self.refs.append((offset + i, t.lambdas.get(x, x)))
163
+ self.refs.append((offset + i, offset_var(t.lambdas.get(x, x), offset)))
130
164
 
131
165
  for i, x in t.args:
132
166
  self.args.append((offset + i, offset + x))
@@ -143,7 +177,10 @@ class L:
143
177
 
144
178
  def call(self, arg: Union[str, "L"]) -> "L":
145
179
  assert self.last_ is not None
146
- self.refs = [(i + 1, x) if i >= self.last_ else (i, x) for (i, x) in self.refs]
180
+ self.refs = [
181
+ (i + 1, offset_var(x, 1)) if i >= self.last_ else (i, x)
182
+ for (i, x) in self.refs
183
+ ]
147
184
  self.args = [
148
185
  (i + 1, x + 1) if i >= self.last_ else (i, x) for (i, x) in self.args
149
186
  ]
@@ -155,7 +192,15 @@ class L:
155
192
  return self
156
193
 
157
194
  def build(self) -> "Term":
158
- self.refs = [(i, self.lambdas.get(x, x)) for i, x in self.refs]
195
+ def bind_var(x: Union[str, int]) -> int:
196
+ # check that all remaining unbound variables are bound to this lambda
197
+ if isinstance(x, int):
198
+ return x
199
+ if x not in self.lambdas:
200
+ raise ValueError(f"variable {x} is not bound to any lambda")
201
+ return self.lambdas[x]
202
+
203
+ self.refs = [(i, bind_var(x)) for i, x in self.refs]
159
204
  ref = pl.from_records(
160
205
  self.refs, orient="row", schema={"id": pl.UInt32, "ref": pl.UInt32}
161
206
  )
curryparty/display.py CHANGED
@@ -38,6 +38,10 @@ def compute_layout(
38
38
  y[node] = y[child] | y[node]
39
39
  return x, y
40
40
 
41
+ def compute_height(nodes: pl.DataFrame):
42
+ _, y = compute_layout(nodes)
43
+ return max(interval[1] for interval in y.values() if interval)
44
+
41
45
 
42
46
  def draw(
43
47
  x: dict[Union[int, tuple[int, int]], Interval],
curryparty/utils.py CHANGED
@@ -56,7 +56,7 @@ class ShapeAnim:
56
56
  self.attributes.add(name)
57
57
  self.values[i, name] = v
58
58
 
59
- def to_element(self, n: int):
59
+ def to_element(self, n: int, begin: str, reset: str):
60
60
  elements = []
61
61
 
62
62
  visible = [
@@ -74,12 +74,24 @@ class ShapeAnim:
74
74
  for i in range(n):
75
75
  if (i, name) not in self.values:
76
76
  self.values[i, name] = non_nulls[0]
77
+ self.shape.__setattr__(name, non_nulls[0])
78
+
77
79
  elements.append(
78
80
  Animate(
79
81
  attributeName=name,
80
82
  values=";".join(str(self.values[i, name]) for i in range(n)),
81
83
  dur=timedelta(seconds=self.duration),
82
- repeatCount="indefinite",
84
+ begin=begin,
85
+ fill="freeze",
86
+ repeatCount="1",
87
+ )
88
+ )
89
+ elements.append(
90
+ Animate(
91
+ attributeName=name,
92
+ values=f"{non_nulls[0]}",
93
+ dur=0,
94
+ begin=reset,
83
95
  )
84
96
  )
85
97
  elements.append(
@@ -87,10 +99,21 @@ class ShapeAnim:
87
99
  attributeName="opacity",
88
100
  values=";".join("1" if v else "0" for v in visible),
89
101
  dur=timedelta(seconds=self.duration),
90
- repeatCount="indefinite",
102
+ begin=begin,
103
+ fill="freeze",
104
+ repeatCount="1",
105
+ )
106
+ )
107
+ elements.append(
108
+ Animate(
109
+ attributeName="opacity",
110
+ values="1" if visible[0] else "0",
111
+ dur=0,
112
+ begin=reset,
91
113
  )
92
114
  )
93
115
 
94
116
  assert not self.shape.elements
117
+ self.shape
95
118
  self.shape.elements = elements
96
119
  return self.shape
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: curryparty
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: Python playground to learn lambda calculus
5
5
  Author: Antonin P
6
6
  Author-email: Antonin P <antonin.peronnet@telecom-paris.fr>
@@ -0,0 +1,8 @@
1
+ curryparty/__init__.py,sha256=kizUbEeGQbTtKEnv9k2PDfCedyXeQoBTWhlLvgCbG1k,7057
2
+ curryparty/core.py,sha256=9vQJlUeYXJ71SkRV6tUiw1RSu7gUJu4f_e6w2Zt-tLQ,3665
3
+ curryparty/display.py,sha256=3u80YvTYBe3mfexng2fiIiNwNAb_S7eYE5afY08f49M,6925
4
+ curryparty/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ curryparty/utils.py,sha256=XvOGyYiIuMcmi-bnBNLdgSQQ69vT0pJ3qJXgwmfhK28,3494
6
+ curryparty-0.2.3.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
7
+ curryparty-0.2.3.dist-info/METADATA,sha256=0h8jSXKNY3_ECpRJJJEpcqru-7zfNKi56Yx5l38Iro8,2139
8
+ curryparty-0.2.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.22
2
+ Generator: uv 0.9.27
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,8 +0,0 @@
1
- curryparty/__init__.py,sha256=VvTj-jDrxDqR9g-tg0IHPx81JAc6xBrRstXzyUpdtu4,5744
2
- curryparty/core.py,sha256=9vQJlUeYXJ71SkRV6tUiw1RSu7gUJu4f_e6w2Zt-tLQ,3665
3
- curryparty/display.py,sha256=8PRRP-9ys-q0OEvIhePxM9di_2n0WRkwA13qK5sq4JU,6783
4
- curryparty/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- curryparty/utils.py,sha256=Q5vYGQ1PvxDPntjF39D-6geAwDDdXz7pVFiAyUGE4WM,2839
6
- curryparty-0.2.0.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
7
- curryparty-0.2.0.dist-info/METADATA,sha256=GfshNERaRDxxfFSXbpKcFf7wEmgRLY6N-yQ-0bhw0sI,2139
8
- curryparty-0.2.0.dist-info/RECORD,,