Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/common/cse-machine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<h1 align="center"> CSE Machine </h1>

<p align="center">A language-agnostic protocol for the Source Academy CSE (Control–Stash–Environment) machine visualizer</p>

<p align="center">
<a href="https://www.npmjs.com/package/@sourceacademy/common-cse-machine"><img src="https://img.shields.io/npm/v/@sourceacademy/common-cse-machine?color=1a2530&label=npm"></a>
<a href="https://github.com/source-academy/plugins/tree/main/src/common/cse-machine"><img src="https://img.shields.io/badge/github-repo-blue?logo=github"></a>
</p>

## Features
- Language-agnostic snapshot protocol — any evaluator (js-slang, py-slang, …) can feed the same CSE machine visualization
- Structured-clone-safe types: everything that crosses the channel is plain JSON, so it survives `MessageChannel` / web-worker boundaries

## Installation
```bash
yarn add @sourceacademy/common-cse-machine
# OR
npm i @sourceacademy/common-cse-machine
# OR
pnpm add @sourceacademy/common-cse-machine
```

## Structure
This package ([`@sourceacademy/common-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine)) contains the shared constants and types required by both the runner-side and web-side plugin implementations.

These include:
- The IDs and channel name the two plugins use to find each other (`RUNNER_ID`, `WEB_ID`, `CSE_CHANNEL`, `CSE_DIRECTORY_ID`)
- The snapshot types that represent a complete evaluation step (`CseSnapshot`, `CseSnapshotMessage`)
- The serialized sub-types for each part of the machine (`CseSerializedInstruction`, `CseSerializedValue`, `CseSerializedEnvFrame`, `CseSerializedBinding`)

## Usage
Import types from `@sourceacademy/common-cse-machine` when implementing a language-specific serializer:

```ts
import type {
CseSnapshot,
CseSerializedValue,
CseSerializedInstruction,
CseSerializedEnvFrame,
} from '@sourceacademy/common-cse-machine';

function buildSnapshot(stepIndex: number): CseSnapshot {
return {
stepIndex,
control: [ { displayText: "call f" } ],
stash: [ { displayValue: "42", label: "number" } ],
environments: [ { id: "e0", name: "global", parentId: null, bindings: [], isActive: true } ],
currentLine: 3,
};
}
```

## Protocol Types

### Constants
| Name | Value | Description |
|------|-------|-------------|
| `CSE_CHANNEL` | `"__cse"` | The channel the runner and host plugins communicate over |
| `RUNNER_ID` | `"__runner_cse"` | ID of the runner-side plugin |
| `WEB_ID` | `"__web_cse"` | ID of the web/host-side plugin |
| `CSE_DIRECTORY_ID` | `"cse-machine"` | Plugin directory lookup key |
| `CSE_MESSAGE_TYPE_SNAPSHOTS` | `"snapshots"` | Discriminator for `CseSnapshotMessage` |

### `CseSnapshot`
A complete snapshot of the CSE machine at a single evaluation step. Arrays are ordered top-first (i.e. top of control/stash is index 0).

| Field | Type | Description |
|-------|------|-------------|
| `stepIndex` | `number` | 0-based index of this step within the run |
| `control` | `CseSerializedInstruction[]` | Control items, top first |
| `stash` | `CseSerializedValue[]` | Stash values, top first |
| `environments` | `CseSerializedEnvFrame[]` | All live environment frames at this step |
| `currentLine` | `number \| undefined` | 1-based source line of the most recently evaluated node |

### `CseSerializedValue`
A single value on the stash or bound in an environment frame.

| Field | Type | Description |
|-------|------|-------------|
| `displayValue` | `string` | Pre-rendered string shown in the visualizer |
| `label` | `string` | Coarse type tag, e.g. `"number"`, `"closure"`, `"list"` |
| `tag` | `string \| undefined` | Optional fine-grained tag for the visualizer |
| `metadata` | `unknown \| undefined` | Language-specific extras (e.g. closure frame id, array element refs) |

### `CseSerializedInstruction`
A single item on the control stack.

| Field | Type | Description |
|-------|------|-------------|
| `displayText` | `string` | Pre-rendered text shown on the control stack |
| `tag` | `string \| undefined` | Optional fine-grained tag for the visualizer |
| `metadata` | `unknown \| undefined` | Typed info used for animation dispatch (e.g. `instrType`, `numOfArgs`, `startLine`) |

### `CseSerializedEnvFrame`
A single environment frame.

| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Stable unique id within a run |
| `name` | `string` | Display name (e.g. `"global"`, function name) |
| `parentId` | `string \| null` | Lexical parent frame id, or `null` for the root |
| `closureFrameId` | `string \| undefined` | For a closure's frame: the id of the frame it was defined in |
| `bindings` | `CseSerializedBinding[]` | Name → value bindings in this frame |
| `heapObjects` | `CseSerializedValue[] \| undefined` | Anonymous heap objects (closures/arrays) not bound to any name |
| `isActive` | `boolean` | Whether this is the currently-active (innermost) frame |
| `isOnCallStack` | `boolean \| undefined` | Whether this frame is currently on the call stack |

### `CseSerializedBinding`
A single name → value binding within a frame.

| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | The bound name |
| `value` | `CseSerializedValue` | The bound value |
| `isConst` | `boolean \| undefined` | Whether the binding is a constant (e.g. `const` in Source) |

## Further reading
- To send snapshots from an evaluator, use [`@sourceacademy/runner-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/runner/cse-machine)
- To receive snapshots in a host app, use [`@sourceacademy/web-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/web/cse-machine)
- The [plugins wiki](https://github.com/source-academy/plugins/wiki) covers how Conductor plugins communicate
60 changes: 60 additions & 0 deletions src/runner/cse-machine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<h1 align="center"> CSE Machine </h1>

<p align="center">Runner-side plugin for the Source Academy CSE machine — sends evaluation snapshots to the host</p>

<p align="center">
<a href="https://www.npmjs.com/package/@sourceacademy/runner-cse-machine"><img src="https://img.shields.io/npm/v/@sourceacademy/runner-cse-machine?color=1a2530&label=npm"></a>
<a href="https://github.com/source-academy/plugins/tree/main/src/runner/cse-machine"><img src="https://img.shields.io/badge/github-repo-blue?logo=github"></a>
</p>

## Features
- Transports a complete run's worth of CSE snapshots from the evaluator (worker) to the host app (browser) over a Conductor channel
- Language-agnostic: any evaluator can use it as long as it serializes its state into `CseSnapshot`s

## Installation
```bash
yarn add @sourceacademy/runner-cse-machine
# OR
npm i @sourceacademy/runner-cse-machine
# OR
pnpm add @sourceacademy/runner-cse-machine
```

## Structure
This package ([`@sourceacademy/runner-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/runner/cse-machine)) contains the `CseMachinePlugin` class — a Conductor runner plugin that an evaluator registers and calls after each run to forward snapshots to the host.

The plugin owns only the transport. Serializing a language's control/stash/environment into [`CseSnapshot`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine/README.md#csesnapshot)s is the evaluator's responsibility and stays in the evaluator repo.

### API Reference
| Name | Description |
|------|-------------|
| `sendSnapshots(snapshots: CseSnapshot[]): void` | Send the full batch of snapshots for a completed run to the host plugin. Call this once per run after all steps have been collected. |

## Usage
After installation, import `CseMachinePlugin` and register it with the Conductor evaluator. After each run, collect your language-specific snapshots, serialize them into [`CseSnapshot`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine/README.md#csesnapshot)s, and call `sendSnapshots`.

```ts
import { CseMachinePlugin } from '@sourceacademy/runner-cse-machine';
import type { CseSnapshot } from '@sourceacademy/common-cse-machine';

// Register the plugin when setting up the evaluator
const cseMachinePlugin = conductorContext.getPlugin(CseMachinePlugin);

// After a run completes, serialize each step into a CseSnapshot
const snapshots: CseSnapshot[] = steps.map((step, i) => ({
stepIndex: i,
control: step.control.map(serializeControlItem),
stash: step.stash.map(serializeValue),
environments: serializeEnvChain(step.environments),
currentLine: step.currentNode?.line,
}));

cseMachinePlugin.sendSnapshots(snapshots);
```

For a full example of how to serialize a language's control/stash/environment into `CseSnapshot`s, see the [py-slang `PyCseMachinePlugin`](https://github.com/source-academy/py-slang/blob/main/src/conductor/plugins/PyCseMachinePlugin.ts).

## Further reading
- For the shared protocol types and IDs, see [`@sourceacademy/common-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine)
- For the host-side plugin that renders snapshots, see [`@sourceacademy/web-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/web/cse-machine)
- The [plugins wiki](https://github.com/source-academy/plugins/wiki) covers how Conductor plugins communicate
63 changes: 63 additions & 0 deletions src/web/cse-machine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<h1 align="center"> CSE Machine </h1>

<p align="center">Web/host-side plugin for the Source Academy CSE machine — receives evaluation snapshots for rendering</p>

<p align="center">
<a href="https://www.npmjs.com/package/@sourceacademy/web-cse-machine"><img src="https://img.shields.io/npm/v/@sourceacademy/web-cse-machine?color=1a2530&label=npm"></a>
<a href="https://github.com/source-academy/plugins/tree/main/src/web/cse-machine"><img src="https://img.shields.io/badge/github-repo-blue?logo=github"></a>
</p>

## Features
- Subscribes to the CSE channel and delivers snapshot batches to the host app's visualization layer
- Language-agnostic: works with any evaluator that sends `CseSnapshot`s via the runner plugin
- Re-exports all protocol types from `@sourceacademy/common-cse-machine` so host apps need only one import

## Installation
In production, use the [plugin directory](https://github.com/source-academy/plugin-directory) hosted on [GitHub Pages](https://source-academy.github.io/plugins/directory.json). It contains a list of all plugins available in this monorepo. Your host app should be able to load plugins from the plugin directory (the Source Academy frontend does).

For locally hosting the plugin repository, run `yarn build` at the root of the repository. Then serve the plugin directory with:
```bash
yarn dlx serve --cors dist/ -p 1915
```
The plugin directory will be available at `http://localhost:1915/directory.json`.

To install the package directly:
```bash
yarn add @sourceacademy/web-cse-machine
# OR
npm i @sourceacademy/web-cse-machine
# OR
pnpm add @sourceacademy/web-cse-machine
```

## Structure
This package ([`@sourceacademy/web-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/web/cse-machine)) contains the `CseMachineHostPlugin` abstract class — a Conductor host plugin that subscribes to the [`CSE_CHANNEL`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine/README.md#constants) and delivers received [`CseSnapshot`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine/README.md#csesnapshot) batches to the host app.

The plugin owns only the transport/receipt. Wiring snapshots into the actual visualization layer (adapter + renderer) is the host app's responsibility and stays in the host repo, because it is tightly coupled to the host's existing CSE machine UI.

### API Reference
| Name | Description |
|------|-------------|
| `abstract receiveSnapshots(snapshots: CseSnapshot[]): void` | Called by the plugin each time a batch of snapshots arrives. Implement this in your host app to wire the snapshots into your visualization layer. |

## Usage
Extend `CseMachineHostPlugin` and implement `receiveSnapshots` to connect incoming snapshots to your rendering layer:

```ts
import { CseMachineHostPlugin } from '@sourceacademy/web-cse-machine';
import type { CseSnapshot } from '@sourceacademy/web-cse-machine'; // re-exported from common

export class MyCseMachinePlugin extends CseMachineHostPlugin {
receiveSnapshots(snapshots: CseSnapshot[]): void {
// Hand snapshots to your visualization layer, e.g.:
cseMachineStore.setSnapshots(snapshots);
}
}
```

For a full example of how the Source Academy frontend wires this into its CSE machine UI, see the [frontend source](https://github.com/source-academy/frontend).

## Further reading
- For the shared protocol types and IDs, see [`@sourceacademy/common-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/common/cse-machine)
- For the runner-side plugin that sends snapshots, see [`@sourceacademy/runner-cse-machine`](https://github.com/source-academy/plugins/tree/main/src/runner/cse-machine)
- The [plugins wiki](https://github.com/source-academy/plugins/wiki) covers how Conductor plugins communicate