Skip to content

Commit 89add1c

Browse files
Module interrupt (#2882)
* Add check for specialError * Remove exhaustive checks * Add a tiny test for special error * Lint modification * Another linting commit
1 parent 489b7e4 commit 89add1c

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Context, interrupt, Result, resume, runFilesInContext } from 'js-slang'
44
import { ACORN_PARSE_OPTIONS, TRY_AGAIN } from 'js-slang/dist/constants';
55
import { InterruptedError } from 'js-slang/dist/errors/errors';
66
import { manualToggleDebugger } from 'js-slang/dist/stdlib/inspector';
7-
import { Chapter, ErrorSeverity, ErrorType, Variant } from 'js-slang/dist/types';
7+
import { Chapter, ErrorSeverity, ErrorType, SourceError, Variant } from 'js-slang/dist/types';
88
import { SagaIterator } from 'redux-saga';
99
import { call, put, race, select, take } from 'redux-saga/effects';
1010
import * as Sourceror from 'sourceror';
@@ -326,17 +326,31 @@ export function* evalCode(
326326
) {
327327
yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv);
328328
if (!isStoriesBlock) {
329-
yield put(actions.evalInterpreterError(context.errors, workspaceLocation));
330-
// enable the CSE machine visualizer during errors
331-
if (context.executionMethod === 'cse-machine' && needUpdateCse) {
332-
yield put(actions.updateStepsTotal(context.runtime.envStepsTotal + 1, workspaceLocation));
333-
yield put(actions.toggleUpdateCse(false, workspaceLocation as any));
334-
yield put(
335-
actions.updateBreakpointSteps(context.runtime.breakpointSteps, workspaceLocation)
336-
);
337-
yield put(
338-
actions.updateChangePointSteps(context.runtime.changepointSteps, workspaceLocation)
339-
);
329+
const specialError = checkSpecialError(context.errors);
330+
if (specialError !== null) {
331+
switch (specialError) {
332+
case 'source_academy_interrupt': {
333+
yield* handleSourceAcademyInterrupt(context, entrypointCode, workspaceLocation);
334+
break;
335+
}
336+
// This should not happen but we check just in case
337+
default: {
338+
yield put(actions.evalInterpreterError(context.errors, workspaceLocation));
339+
}
340+
}
341+
} else {
342+
yield put(actions.evalInterpreterError(context.errors, workspaceLocation));
343+
// enable the CSE machine visualizer during errors
344+
if (context.executionMethod === 'cse-machine' && needUpdateCse) {
345+
yield put(actions.updateStepsTotal(context.runtime.envStepsTotal + 1, workspaceLocation));
346+
yield put(actions.toggleUpdateCse(false, workspaceLocation as any));
347+
yield put(
348+
actions.updateBreakpointSteps(context.runtime.breakpointSteps, workspaceLocation)
349+
);
350+
yield put(
351+
actions.updateChangePointSteps(context.runtime.changepointSteps, workspaceLocation)
352+
);
353+
}
340354
}
341355
} else {
342356
// Safe to use ! as storyEnv will be defined from above when we call from EVAL_STORY
@@ -412,3 +426,34 @@ export function* evalCode(
412426
introIcon && introIcon.classList.remove('side-content-tab-alert-error');
413427
}
414428
}
429+
430+
// Special module errors
431+
const specialErrors = ['source_academy_interrupt'] as const;
432+
type SpecialError = (typeof specialErrors)[number];
433+
434+
function checkSpecialError(errors: SourceError[]): SpecialError | null {
435+
if (errors.length !== 1) {
436+
return null;
437+
}
438+
const firstError = errors[0] as any;
439+
if (typeof firstError.error !== 'string') {
440+
return null;
441+
}
442+
if (!specialErrors.includes(firstError.error)) {
443+
return null;
444+
}
445+
446+
return firstError.error as SpecialError;
447+
}
448+
449+
function* handleSourceAcademyInterrupt(
450+
context: Context,
451+
entrypointCode: string,
452+
workspaceLocation: WorkspaceLocation
453+
) {
454+
yield put(
455+
actions.evalInterpreterSuccess('Program has been interrupted by module', workspaceLocation)
456+
);
457+
context.errors = [];
458+
yield put(actions.notifyProgramEvaluated(null, null, entrypointCode, context, workspaceLocation));
459+
}

src/commons/sagas/__tests__/WorkspaceSaga.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Context, IOptions, Result, resume, runFilesInContext, runInContext } from 'js-slang';
22
import createContext from 'js-slang/dist/createContext';
3-
import { Chapter, Finished, Variant } from 'js-slang/dist/types';
3+
import { Chapter, ErrorType, Finished, SourceError, Variant } from 'js-slang/dist/types';
44
import { call } from 'redux-saga/effects';
55
import { expectSaga } from 'redux-saga-test-plan';
66
import * as matchers from 'redux-saga-test-plan/matchers';
@@ -1096,6 +1096,32 @@ describe('evalCode', () => {
10961096
.silentRun();
10971097
});
10981098
});
1099+
1100+
describe('special error', () => {
1101+
test('on throwing of special error, calls evalInterpreterSuccess ', async () => {
1102+
context.errors = [
1103+
{ type: ErrorType.RUNTIME, error: 'source_academy_interrupt' } as unknown as SourceError
1104+
];
1105+
return expectSaga(
1106+
evalCode,
1107+
files,
1108+
codeFilePath,
1109+
context,
1110+
execTime,
1111+
workspaceLocation,
1112+
actionType
1113+
)
1114+
.withState(state)
1115+
.provide([
1116+
[
1117+
call(runFilesInContext, files, codeFilePath, context, options),
1118+
{ status: 'error', value }
1119+
]
1120+
])
1121+
.put(evalInterpreterSuccess('Program has been interrupted by module', workspaceLocation))
1122+
.silentRun();
1123+
});
1124+
});
10991125
});
11001126

11011127
describe('evalTestCode', () => {

0 commit comments

Comments
 (0)