Skip to content

Commit f74cd15

Browse files
committed
code registry
1 parent 21f1174 commit f74cd15

File tree

1 file changed

+208
-3
lines changed

1 file changed

+208
-3
lines changed

packages/javascript-kernel/src/executor.ts

Lines changed: 208 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the terms of the Modified BSD License.
33

44
import { parseScript } from 'meriyah';
5+
import { generate } from 'astring';
56

67
import { IMimeBundle } from './display';
78

@@ -72,6 +73,21 @@ export interface IInspectResult {
7273
metadata: Record<string, any>;
7374
}
7475

76+
/**
77+
* Registry for tracking code declarations across cells.
78+
* Allows deduplication - later definitions override earlier ones.
79+
*/
80+
export interface ICodeRegistry {
81+
/** Function declarations by name (setup, draw, etc.) */
82+
functions: Map<string, any>;
83+
/** Variable declarations by name */
84+
variables: Map<string, any>;
85+
/** Class declarations by name */
86+
classes: Map<string, any>;
87+
/** Other top-level statements (expressions, etc.) in execution order */
88+
statements: any[];
89+
}
90+
7591
/**
7692
* Configuration for the JavaScript executor.
7793
*/
@@ -282,6 +298,191 @@ export class JavaScriptExecutor {
282298
return lines.join('\n');
283299
}
284300

301+
/**
302+
* Create a new empty code registry.
303+
*/
304+
createCodeRegistry(): ICodeRegistry {
305+
return {
306+
functions: new Map(),
307+
variables: new Map(),
308+
classes: new Map(),
309+
statements: []
310+
};
311+
}
312+
313+
/**
314+
* Register code from executed cells into the registry.
315+
* Later definitions of the same name will override earlier ones.
316+
* Import declarations are skipped (handled separately).
317+
*
318+
* @param code - The code to register.
319+
* @param registry - The registry to add declarations to.
320+
*/
321+
registerCode(code: string, registry: ICodeRegistry): void {
322+
if (code.trim().length === 0) {
323+
return;
324+
}
325+
326+
try {
327+
const ast = parseScript(code, {
328+
ranges: true,
329+
module: true
330+
});
331+
332+
for (const node of ast.body) {
333+
switch (node.type) {
334+
case 'FunctionDeclaration':
335+
// Store function by name - later definitions override
336+
if (node.id && node.id.name) {
337+
registry.functions.set(node.id.name, node);
338+
}
339+
break;
340+
341+
case 'ClassDeclaration':
342+
// Store class by name - later definitions override
343+
if (node.id && node.id.name) {
344+
registry.classes.set(node.id.name, node);
345+
}
346+
break;
347+
348+
case 'VariableDeclaration':
349+
// For variable declarations, extract each declarator
350+
for (const declarator of node.declarations) {
351+
if (declarator.id.type === 'Identifier') {
352+
// Store the whole declaration node with just this declarator
353+
const singleDecl = {
354+
...node,
355+
declarations: [declarator]
356+
};
357+
registry.variables.set(declarator.id.name, singleDecl);
358+
} else if (declarator.id.type === 'ObjectPattern') {
359+
// Handle destructuring: const { a, b } = obj
360+
for (const prop of declarator.id.properties) {
361+
if (
362+
prop.type === 'Property' &&
363+
prop.key.type === 'Identifier'
364+
) {
365+
const name =
366+
prop.value?.type === 'Identifier'
367+
? prop.value.name
368+
: prop.key.name;
369+
registry.variables.set(name, {
370+
...node,
371+
declarations: [declarator],
372+
_destructuredName: name
373+
});
374+
}
375+
}
376+
} else if (declarator.id.type === 'ArrayPattern') {
377+
// Handle array destructuring: const [a, b] = arr
378+
for (const element of declarator.id.elements) {
379+
if (element && element.type === 'Identifier') {
380+
registry.variables.set(element.name, {
381+
...node,
382+
declarations: [declarator],
383+
_destructuredName: element.name
384+
});
385+
}
386+
}
387+
}
388+
}
389+
break;
390+
391+
case 'ImportDeclaration':
392+
// Skip imports - handled separately via extractImports
393+
break;
394+
395+
case 'ExpressionStatement':
396+
// For expression statements, we need to track them
397+
if (
398+
node.expression.type === 'AssignmentExpression' &&
399+
node.expression.left.type === 'Identifier'
400+
) {
401+
// Named assignment like `x = 5;`
402+
registry.statements.push(node);
403+
} else {
404+
// Other expressions (function calls, etc.) - keep in order
405+
registry.statements.push(node);
406+
}
407+
break;
408+
409+
default:
410+
// Other statements (if, for, while, etc.) - keep in order
411+
registry.statements.push(node);
412+
break;
413+
}
414+
}
415+
} catch {
416+
// If parsing fails, we can't register the code
417+
}
418+
}
419+
420+
/**
421+
* Generate code from the registry.
422+
* Produces clean, deduplicated code for regeneration scenarios.
423+
* Includes globalThis assignments so declarations are accessible globally.
424+
*
425+
* @param registry - The registry to generate code from.
426+
* @returns Generated JavaScript code string.
427+
*/
428+
generateCodeFromRegistry(registry: ICodeRegistry): string {
429+
const programBody: any[] = [];
430+
const globalAssignments: string[] = [];
431+
432+
// Add variables first (they might be used by functions)
433+
const seenDestructuringDecls = new Set<string>();
434+
for (const [name, node] of registry.variables) {
435+
// For destructuring, only add once per actual declaration
436+
if (node._destructuredName) {
437+
const declKey = generate(node.declarations[0]);
438+
if (seenDestructuringDecls.has(declKey)) {
439+
continue;
440+
}
441+
seenDestructuringDecls.add(declKey);
442+
// Remove the marker before generating
443+
const cleanNode = { ...node };
444+
delete cleanNode._destructuredName;
445+
programBody.push(cleanNode);
446+
} else {
447+
programBody.push(node);
448+
}
449+
globalAssignments.push(`globalThis["${name}"] = ${name};`);
450+
}
451+
452+
// Add classes
453+
for (const [name, node] of registry.classes) {
454+
programBody.push(node);
455+
globalAssignments.push(`globalThis["${name}"] = ${name};`);
456+
}
457+
458+
// Add functions
459+
for (const [name, node] of registry.functions) {
460+
programBody.push(node);
461+
globalAssignments.push(`globalThis["${name}"] = ${name};`);
462+
}
463+
464+
// Add other statements in order
465+
for (const node of registry.statements) {
466+
programBody.push(node);
467+
}
468+
469+
// Create a program AST and generate code
470+
const program = {
471+
type: 'Program',
472+
body: programBody,
473+
sourceType: 'script'
474+
};
475+
476+
// Generate the code and append globalThis assignments
477+
const generatedCode = generate(program);
478+
479+
if (globalAssignments.length > 0) {
480+
return generatedCode + '\n' + globalAssignments.join('\n');
481+
}
482+
483+
return generatedCode;
484+
}
485+
285486
/**
286487
* Get MIME bundle for a value.
287488
* Supports custom output methods:
@@ -431,9 +632,13 @@ export class JavaScriptExecutor {
431632

432633
// Handle generic objects
433634
if (typeof value === 'object') {
434-
// Check if it's already a mime bundle
435-
if ('data' in value && typeof value.data === 'object') {
436-
return value.data;
635+
// Check if it's already a mime bundle (has data with MIME-type keys)
636+
if (value.data && typeof value.data === 'object') {
637+
const dataKeys = Object.keys(value.data);
638+
const hasMimeKeys = dataKeys.some(key => key.includes('/'));
639+
if (hasMimeKeys) {
640+
return value.data;
641+
}
437642
}
438643

439644
try {

0 commit comments

Comments
 (0)