Skip to content

Integrate Conductor Interface for py-slang Runner #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 24, 2025
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,16 @@ The AST types need to be regenerated after changing
the AST type definitions in `generate-ast.ts`.
```shell
npm run regen
```
```

## Acknowledgements

This project adapts the `Conductor Interface` from [source-academy/conductor](https://github.com/source-academy/conductor), which is part of the Source Academy ecosystem.

Specifically, all files under the following folders are derived from the conductor repository:

- `src/conductor/`
- `src/common/`
- `src/conduit/`

All credits go to the original authors of the Source Academy Conductor Interface.
8 changes: 8 additions & 0 deletions src/common/Constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

export const enum Constant {
PROTOCOL_VERSION = 0,
PROTOCOL_MIN_VERSION = 0,
}
31 changes: 31 additions & 0 deletions src/common/ds/MessageQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { Queue } from "./Queue";

export class MessageQueue<T> {
private readonly __inputQueue: Queue<T> = new Queue();
private readonly __promiseQueue: Queue<Function> = new Queue();

push(item: T) {
if (this.__promiseQueue.length !== 0) this.__promiseQueue.pop()(item);
else this.__inputQueue.push(item);
}

async pop(): Promise<T> {
if (this.__inputQueue.length !== 0) return this.__inputQueue.pop();
return new Promise((resolve, _reject) => {
this.__promiseQueue.push(resolve);
});
}

tryPop(): T | undefined {
if (this.__inputQueue.length !== 0) return this.__inputQueue.pop();
return undefined;
}

constructor() {
this.push = this.push.bind(this);
}
}
55 changes: 55 additions & 0 deletions src/common/ds/Queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

/**
* A stack-based queue implementation.
* `push` and `pop` run in amortized constant time.
*/
export class Queue<T> {
/** The output stack. */
private __s1: T[] = [];
/** The input stack. */
private __s2: T[] = [];

/**
* Adds an item to the queue.
* @param item The item to be added to the queue.
*/
push(item: T) {
this.__s2.push(item);
}

/**
* Removes an item from the queue.
* @returns The item removed from the queue.
* @throws If the queue is empty.
*/
pop(): T {
if (this.__s1.length === 0) {
if (this.__s2.length === 0) throw new Error("queue is empty");
let temp = this.__s1;
this.__s1 = this.__s2.reverse();
this.__s2 = temp;
}
return this.__s1.pop()!; // as the length is nonzero
}

/**
* The length of the queue.
*/
get length() {
return this.__s1.length + this.__s2.length;
}

/**
* Makes a copy of the queue.
* @returns A copy of the queue.
*/
clone(): Queue<T> {
const newQueue = new Queue<T>();
newQueue.__s1 = [...this.__s1];
newQueue.__s2 = [...this.__s2];
return newQueue;
}
}
6 changes: 6 additions & 0 deletions src/common/ds/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

export { MessageQueue } from "./MessageQueue";
export { Queue } from "./Queue";
17 changes: 17 additions & 0 deletions src/common/errors/ConductorError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ErrorType } from "./ErrorType";

/**
* Generic Conductor Error.
*/
export class ConductorError extends Error {
override name = "ConductorError";
readonly errorType: ErrorType | string = ErrorType.UNKNOWN;

constructor(message: string) {
super(message);
}
}
18 changes: 18 additions & 0 deletions src/common/errors/ConductorInternalError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ConductorError } from "./ConductorError";
import { ErrorType } from "./ErrorType";

/**
* Conductor internal error, probably caused by developer oversight.
*/
export class ConductorInternalError extends ConductorError {
override name = "ConductorInternalError";
override readonly errorType: ErrorType | string = ErrorType.INTERNAL;

constructor(message: string) {
super(message);
}
}
12 changes: 12 additions & 0 deletions src/common/errors/ErrorType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

export const enum ErrorType {
UNKNOWN = "__unknown",
INTERNAL = "__internal",
EVALUATOR = "__evaluator",
EVALUATOR_SYNTAX = "__evaluator_syntax",
EVALUATOR_TYPE = "__evaluator_type",
EVALUATOR_RUNTIME = "__evaluator_runtime",
}
30 changes: 30 additions & 0 deletions src/common/errors/EvaluatorError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ConductorError } from "./ConductorError";
import { ErrorType } from "./ErrorType";

/**
* Generic evaluation error, caused by a problem in user code.
*/
export class EvaluatorError extends ConductorError {
override name = "EvaluatorError";
override readonly errorType: ErrorType | string = ErrorType.EVALUATOR;

readonly rawMessage: string;
readonly line?: number;
readonly column?: number;
readonly fileName?: string

constructor(message: string, line?: number, column?: number, fileName?: string) {
const location = line !== undefined
? `${fileName ? fileName + ":" : ""}${line}${column !== undefined ? ":" + column : ""}: `
: "";
super(`${location}${message}`);
this.rawMessage = message;
this.line = line;
this.column = column;
this.fileName = fileName
}
}
14 changes: 14 additions & 0 deletions src/common/errors/EvaluatorRuntimeError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ErrorType } from "./ErrorType";
import { EvaluatorError } from "./EvaluatorError";

/**
* Evaluator runtime error - some problem occurred while running the user code.
*/
export class EvaluatorRuntimeError extends EvaluatorError {
override name = "EvaluatorRuntimeError";
override readonly errorType: ErrorType | string = ErrorType.EVALUATOR_RUNTIME;
}
14 changes: 14 additions & 0 deletions src/common/errors/EvaluatorSyntaxError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ErrorType } from "./ErrorType";
import { EvaluatorError } from "./EvaluatorError";

/**
* Evaluator syntax error - the user code does not follow the evaluator's prescribed syntax.
*/
export class EvaluatorSyntaxError extends EvaluatorError {
override name = "EvaluatorSyntaxError";
override readonly errorType: ErrorType | string = ErrorType.EVALUATOR_SYNTAX;
}
26 changes: 26 additions & 0 deletions src/common/errors/EvaluatorTypeError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ConductorError } from "./ConductorError";
import { ErrorType } from "./ErrorType";
import { EvaluatorError } from "./EvaluatorError";

/**
* Evaluator type error - the user code is not well typed or provides values of incorrect type to external functions.
*/
export class EvaluatorTypeError extends EvaluatorError {
override name = "EvaluatorTypeError";
override readonly errorType: ErrorType | string = ErrorType.EVALUATOR_TYPE;

override readonly rawMessage: string;
readonly expected: string;
readonly actual: string;

constructor(message: string, expected: string, actual: string, line?: number, column?: number, fileName?: string) {
super(`${message} (expected ${expected}, got ${actual})`, line, column, fileName);
this.rawMessage = message;
this.expected = expected;
this.actual = actual;
}
}
8 changes: 8 additions & 0 deletions src/common/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

export { ConductorError } from "./ConductorError";
export { ConductorInternalError } from "./ConductorInternalError";
export { ErrorType } from "./ErrorType";
export { EvaluatorTypeError } from "./EvaluatorTypeError";
14 changes: 14 additions & 0 deletions src/common/util/InvalidModuleError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { ConductorError } from "../errors";

export class InvalidModuleError extends ConductorError {
override name = "InvalidModuleError";
override readonly errorType = "__invalidmodule";

constructor() {
super("Not a module");
}
}
18 changes: 18 additions & 0 deletions src/common/util/importExternalModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import type { IModulePlugin } from "../../conductor/module";
import { PluginClass } from "../../conduit/types";
import { importExternalPlugin } from "./importExternalPlugin";

/**
* Imports an external module from a given location.
* @param location Where to find the external module.
* @returns A promise resolving to the imported module.
*/
export async function importExternalModule(location: string): Promise<PluginClass<any, IModulePlugin>> {
const plugin = await importExternalPlugin(location) as PluginClass<any, IModulePlugin>;
// TODO: additional verification it is a module
return plugin;
}
16 changes: 16 additions & 0 deletions src/common/util/importExternalPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

import { PluginClass } from "../../conduit/types";

/**
* Imports an external plugin from a given location.
* @param location Where to find the external plugin.
* @returns A promise resolving to the imported plugin.
*/
export async function importExternalPlugin(location: string): Promise<PluginClass> {
const plugin = (await import(/* webpackIgnore: true */ location)).plugin as PluginClass;
// TODO: verify it is actually a plugin
return plugin;
}
7 changes: 7 additions & 0 deletions src/common/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file is adapted from:
// https://github.com/source-academy/conductor
// Original author(s): Source Academy Team

export { importExternalModule } from "./importExternalModule";
export { importExternalPlugin } from "./importExternalPlugin";
export { InvalidModuleError } from "./InvalidModuleError";
Loading