Skip to content

Commit 02f7784

Browse files
authored
Merge pull request #312 from githubnext/autoloop/build-tsb-pandas-typescript-migration
[Autoloop: build-tsb-pandas-typescript-migration]
2 parents 32cbf31 + ac9d5fd commit 02f7784

13 files changed

Lines changed: 1168 additions & 132 deletions

File tree

playground/errors.html

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>tsb — pd.errors</title>
7+
<style>
8+
:root {
9+
--bg: #0d1117;
10+
--surface: #161b22;
11+
--border: #30363d;
12+
--text: #e6edf3;
13+
--accent: #58a6ff;
14+
--green: #3fb950;
15+
--orange: #d29922;
16+
--red: #f85149;
17+
--font-mono: "Cascadia Code", "Fira Code", "JetBrains Mono", monospace;
18+
}
19+
* { box-sizing: border-box; margin: 0; padding: 0; }
20+
body {
21+
background: var(--bg);
22+
color: var(--text);
23+
font-family: system-ui, -apple-system, sans-serif;
24+
line-height: 1.6;
25+
padding: 2rem;
26+
max-width: 900px;
27+
margin: 0 auto;
28+
}
29+
a { color: var(--accent); }
30+
h1 { color: var(--accent); margin-bottom: 0.5rem; }
31+
h2 { margin-top: 0; margin-bottom: 0.5rem; font-size: 1.25rem; }
32+
p { color: #8b949e; margin-bottom: 1rem; }
33+
code {
34+
font-family: var(--font-mono);
35+
font-size: 0.875em;
36+
background: var(--surface);
37+
border: 1px solid var(--border);
38+
border-radius: 0.3rem;
39+
padding: 0.1rem 0.4rem;
40+
}
41+
.back { margin-bottom: 2rem; display: inline-block; }
42+
.subtitle { margin-bottom: 1.5rem; }
43+
44+
#playground-loading {
45+
position: fixed; inset: 0;
46+
background: rgba(13, 17, 23, 0.92);
47+
display: flex; flex-direction: column;
48+
align-items: center; justify-content: center;
49+
z-index: 1000; gap: 1rem;
50+
}
51+
.spinner {
52+
width: 40px; height: 40px;
53+
border: 3px solid var(--border);
54+
border-top-color: var(--accent);
55+
border-radius: 50%;
56+
animation: spin 0.8s linear infinite;
57+
}
58+
@keyframes spin { to { transform: rotate(360deg); } }
59+
#playground-status { color: #8b949e; font-size: 0.95rem; }
60+
61+
.section {
62+
background: var(--surface);
63+
border: 1px solid var(--border);
64+
border-radius: 0.75rem;
65+
padding: 1.5rem;
66+
margin-bottom: 1.5rem;
67+
}
68+
.section p { margin-bottom: 0.75rem; }
69+
70+
.playground-block { margin-top: 0.75rem; }
71+
.playground-header {
72+
display: flex; align-items: center; justify-content: space-between;
73+
background: #1c2128;
74+
border: 1px solid var(--border);
75+
border-bottom: none;
76+
border-radius: 0.5rem 0.5rem 0 0;
77+
padding: 0.4rem 0.75rem;
78+
}
79+
.playground-label {
80+
font-size: 0.75rem; color: #8b949e;
81+
text-transform: uppercase; letter-spacing: 0.05em;
82+
}
83+
.playground-actions { display: flex; gap: 0.5rem; }
84+
.playground-actions button {
85+
background: transparent; color: var(--accent);
86+
border: 1px solid var(--border);
87+
border-radius: 0.35rem;
88+
padding: 0.25rem 0.7rem;
89+
font-size: 0.8rem; cursor: pointer;
90+
font-family: system-ui, sans-serif;
91+
transition: background 0.15s, border-color 0.15s;
92+
}
93+
.playground-actions button:hover:not(:disabled) {
94+
background: rgba(88, 166, 255, 0.1);
95+
border-color: var(--accent);
96+
}
97+
.playground-actions button:disabled { opacity: 0.4; cursor: not-allowed; }
98+
.playground-run { font-weight: 600; }
99+
100+
.playground-editor {
101+
display: block; width: 100%; min-height: 80px;
102+
background: #0d1117; color: var(--text);
103+
border: 1px solid var(--border);
104+
border-top: none; border-bottom: none;
105+
padding: 1rem;
106+
font-family: var(--font-mono);
107+
font-size: 0.875rem; line-height: 1.55;
108+
resize: vertical; outline: none;
109+
tab-size: 2; white-space: pre; overflow-x: auto;
110+
}
111+
.playground-editor:focus {
112+
border-color: var(--accent);
113+
box-shadow: inset 0 0 0 1px var(--accent);
114+
}
115+
116+
.playground-output {
117+
background: #1c2333;
118+
border: 1px solid var(--border);
119+
border-radius: 0 0 0.5rem 0.5rem;
120+
padding: 0.75rem 1rem;
121+
font-family: var(--font-mono);
122+
font-size: 0.85rem; color: #8b949e;
123+
white-space: pre-wrap; min-height: 2rem;
124+
word-break: break-word;
125+
}
126+
.playground-output.active { color: var(--green); border-color: var(--green); }
127+
.playground-output.error { color: var(--red); border-color: var(--red); }
128+
.playground-hint {
129+
font-size: 0.75rem; color: #484f58;
130+
margin-top: 0.35rem; text-align: right;
131+
}
132+
133+
footer {
134+
text-align: center;
135+
padding: 2rem 0;
136+
color: #8b949e;
137+
font-size: 0.85rem;
138+
border-top: 1px solid var(--border);
139+
margin-top: 2rem;
140+
}
141+
</style>
142+
</head>
143+
<body>
144+
145+
<div id="playground-loading">
146+
<div class="spinner"></div>
147+
<div id="playground-status">Initializing playground…</div>
148+
</div>
149+
150+
<a class="back" href="index.html">← Back to roadmap</a>
151+
<h1>pd.errors</h1>
152+
<p class="subtitle">Pandas-compatible error and warning classes — mirrors Python's <code>pd.errors</code> module.
153+
All classes extend native <code>Error</code> and integrate with <code>try/catch</code> and <code>instanceof</code>.</p>
154+
155+
<div class="section">
156+
<h2>1 — Base classes: ValueError, KeyError, IndexError</h2>
157+
<p>Three base classes mirror Python's built-in exceptions. They extend native JS error types so they
158+
work with standard error-handling idioms.</p>
159+
<div class="playground-block">
160+
<div class="playground-header">
161+
<span class="playground-label">TypeScript</span>
162+
<div class="playground-actions">
163+
<button class="playground-run" disabled>▶ Run</button>
164+
<button class="playground-reset">↺ Reset</button>
165+
</div>
166+
</div>
167+
<textarea class="playground-editor" spellcheck="false">import { ValueError, KeyError, IndexError } from "tsb";
168+
169+
const ve = new ValueError("bad value");
170+
console.log(ve instanceof TypeError); // true (ValueError extends TypeError)
171+
console.log(ve.name); // "ValueError"
172+
173+
const ke = new KeyError("missing key");
174+
console.log(ke instanceof Error); // true
175+
console.log(ke.name); // "KeyError"
176+
177+
const ie = new IndexError("out of bounds");
178+
console.log(ie instanceof RangeError); // true
179+
console.log(ie.name); // "IndexError"</textarea>
180+
<div class="playground-output">Click ▶ Run to execute</div>
181+
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
182+
</div>
183+
</div>
184+
185+
<div class="section">
186+
<h2>2 — Catching specific errors with instanceof</h2>
187+
<p>Use <code>instanceof</code> in catch blocks to handle specific error types — just like Python's
188+
<code>except SpecificError</code>.</p>
189+
<div class="playground-block">
190+
<div class="playground-header">
191+
<span class="playground-label">TypeScript</span>
192+
<div class="playground-actions">
193+
<button class="playground-run" disabled>▶ Run</button>
194+
<button class="playground-reset">↺ Reset</button>
195+
</div>
196+
</div>
197+
<textarea class="playground-editor" spellcheck="false">import { ParserError, EmptyDataError, MergeError } from "tsb";
198+
199+
function riskyOperation(kind: string): void {
200+
if (kind === "parse") throw new ParserError("invalid CSV format");
201+
if (kind === "empty") throw new EmptyDataError();
202+
if (kind === "merge") throw new MergeError("incompatible merge keys");
203+
}
204+
205+
for (const kind of ["parse", "empty", "merge"]) {
206+
try {
207+
riskyOperation(kind);
208+
} catch (e) {
209+
if (e instanceof EmptyDataError) {
210+
console.log("Empty file:", e.message);
211+
} else if (e instanceof ParserError) {
212+
console.log("Parse error:", e.message);
213+
} else if (e instanceof MergeError) {
214+
console.log("Merge error:", e.message);
215+
}
216+
}
217+
}</textarea>
218+
<div class="playground-output">Click ▶ Run to execute</div>
219+
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
220+
</div>
221+
</div>
222+
223+
<div class="section">
224+
<h2>3 — The errors namespace (pd.errors style)</h2>
225+
<p>All error classes are grouped under the <code>errors</code> namespace, mirroring
226+
Python's <code>pd.errors.ParserError</code> etc.</p>
227+
<div class="playground-block">
228+
<div class="playground-header">
229+
<span class="playground-label">TypeScript</span>
230+
<div class="playground-actions">
231+
<button class="playground-run" disabled>▶ Run</button>
232+
<button class="playground-reset">↺ Reset</button>
233+
</div>
234+
</div>
235+
<textarea class="playground-editor" spellcheck="false">import { errors } from "tsb";
236+
237+
// Access any error class via the namespace
238+
const e = new errors.MergeError("incompatible merge keys");
239+
console.log(e instanceof errors.MergeError); // true
240+
console.log(e instanceof errors.ValueError); // true (MergeError extends ValueError)
241+
console.log(e.name); // "MergeError"
242+
243+
// OutOfBoundsDatetime with default message
244+
const dt = new errors.OutOfBoundsDatetime();
245+
console.log(dt.message); // "Out of bounds nanosecond timestamp"
246+
console.log(dt instanceof errors.ValueError); // true</textarea>
247+
<div class="playground-output">Click ▶ Run to execute</div>
248+
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
249+
</div>
250+
</div>
251+
252+
<div class="section">
253+
<h2>4 — AbstractMethodError for extension classes</h2>
254+
<p><code>AbstractMethodError</code> is thrown when a subclass forgets to implement a required method —
255+
mirroring Python's <code>raise NotImplementedError</code>.</p>
256+
<div class="playground-block">
257+
<div class="playground-header">
258+
<span class="playground-label">TypeScript</span>
259+
<div class="playground-actions">
260+
<button class="playground-run" disabled>▶ Run</button>
261+
<button class="playground-reset">↺ Reset</button>
262+
</div>
263+
</div>
264+
<textarea class="playground-editor" spellcheck="false">import { AbstractMethodError } from "tsb";
265+
266+
class BaseTransform {
267+
transform(_value: number): number {
268+
throw new AbstractMethodError("BaseTransform.transform");
269+
}
270+
}
271+
272+
class DoubleTransform extends BaseTransform {
273+
override transform(value: number): number {
274+
return value * 2;
275+
}
276+
}
277+
278+
const t = new DoubleTransform();
279+
console.log(t.transform(5)); // 10
280+
281+
try {
282+
const base = new BaseTransform();
283+
base.transform(5);
284+
} catch (e) {
285+
if (e instanceof AbstractMethodError) {
286+
console.log("Abstract method called:", e.message);
287+
}
288+
}</textarea>
289+
<div class="playground-output">Click ▶ Run to execute</div>
290+
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
291+
</div>
292+
</div>
293+
294+
<footer>
295+
<p>
296+
<a href="index.html">tsb playground</a> ·
297+
Built by <a href="https://github.com/githubnext/autoloop">Autoloop</a>
298+
</p>
299+
</footer>
300+
301+
<script type="module" src="playground-runtime.js"></script>
302+
</body>
303+
</html>

src/core/align.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ function resolveIndex(left: Index<Label>, right: Index<Label>, join: JoinHow): I
9292
return left;
9393
case "right":
9494
return right;
95+
default:
96+
return left.union(right);
9597
}
9698
}
9799

@@ -161,8 +163,14 @@ export function alignDataFrame(
161163
const { join = "outer", fillValue = null, axis } = options;
162164

163165
// Normalise axis: null/undefined → align both
164-
const normalised: 0 | 1 | null =
165-
axis === null || axis === undefined ? null : axis === 0 || axis === "index" ? 0 : 1;
166+
let normalised: 0 | 1 | null;
167+
if (axis === null || axis === undefined) {
168+
normalised = null;
169+
} else if (axis === 0 || axis === "index") {
170+
normalised = 0;
171+
} else {
172+
normalised = 1;
173+
}
166174

167175
const alignRows = normalised === null || normalised === 0;
168176
const alignCols = normalised === null || normalised === 1;

0 commit comments

Comments
 (0)