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
17 changes: 16 additions & 1 deletion src/bundles/repl/src/__tests__/stringify_value.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DataType, type TypedValue } from '@sourceacademy/conductor/types';
import { TestDataHandler, booleanValue, emptyListValue, numberValue, stringValue, voidValue } from '@sourceacademy/modules-testplugin';
import { describe, expect, test } from 'vitest';
import { stringifyReplValue } from '../stringify_value';
import { hasNothingToDisplay, stringifyReplValue } from '../stringify_value';

describe(stringifyReplValue, () => {
test('VOID stringifies as "undefined"', async () => {
Expand Down Expand Up @@ -103,3 +103,18 @@ describe(stringifyReplValue, () => {
expect(result).toContain('...');
});
});

describe(hasNothingToDisplay, () => {
test('VOID and EMPTY_LIST (a Python None) have nothing to display', () => {
expect(hasNothingToDisplay(voidValue())).toBe(true);
expect(hasNothingToDisplay(emptyListValue())).toBe(true);
});

test.each([
['BOOLEAN', booleanValue(false)],
['NUMBER', numberValue(0)],
['CONST_STRING', stringValue('')]
])('a %s value is displayed, even when falsy', (_, value) => {
expect(hasNothingToDisplay(value)).toBe(false);
});
});
15 changes: 10 additions & 5 deletions src/bundles/repl/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
type ReplSetProgramTextMessage
} from './protocol';
import { processRichDisplayContent } from './rich_display';
import { stringifyReplValue } from './stringify_value';
import { hasNothingToDisplay, stringifyReplValue } from './stringify_value';

type ReplTabLoader = {
tabs: string[];
Expand Down Expand Up @@ -368,10 +368,15 @@ export default class ReplModulePlugin extends BaseModulePlugin {
}
const result = step.value;

const content = result.type === DataType.CONST_STRING
? result.value
: await stringifyReplValue(this.evaluator, result);
this.__displayOutput({ content, color: COLOR_RUN_CODE_RESULT, outputMethod: 'plaintext' });
// An evaluator without a result (a Python function that returns None crosses the boundary
// as EMPTY_LIST, see #877) has nothing to echo; showing it as text would print a stray
// "null" after everything the evaluator displayed itself.
if (!hasNothingToDisplay(result)) {
const content = result.type === DataType.CONST_STRING
? result.value
: await stringifyReplValue(this.evaluator, result);
this.__displayOutput({ content, color: COLOR_RUN_CODE_RESULT, outputMethod: 'plaintext' });
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
this.__displayOutput({ content: message, color: COLOR_ERROR_MESSAGE, outputMethod: 'plaintext' });
Expand Down
11 changes: 11 additions & 0 deletions src/bundles/repl/src/stringify_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ const MAX_NODES = 10_000;

type Budget = { remaining: number };

/**
* Whether an evaluator's result is "no value" and the Repl tab should show nothing for the run,
* like Python's REPL does for `None`. A Python `None` crosses the Conductor boundary as
* DataType.EMPTY_LIST (it has no type of its own - see issue #877), and a function without a
* result as DataType.VOID; rendering either as text would show a bare `null` or `undefined` after
* every run of an evaluator that, say, displays its own output.
*/
export function hasNothingToDisplay(value: TypedValue<DataType>): boolean {
return value.type === DataType.VOID || value.type === DataType.EMPTY_LIST;
}

export async function stringifyReplValue(evaluator: IDataHandler, value: TypedValue<DataType>): Promise<string> {
return stringifyReplValueDepth(evaluator, value, 0, { remaining: MAX_NODES });
}
Expand Down
Loading