import { apply , call , cancel , delay , fork , put , race , select , take , takeEvery , takeLatest , } from 'redux-saga/effects' ; import * as C from '@france-ioi/persistent-c' ; import { default as ApiBundle , makeContext , performStep , StepperContext , StepperContextParameters , StepperError , } from './api' ; import CompileBundle , { CompileStatus } from './compile' ; import EffectsBundle from './c/effects' ; import DelayBundle from './delay' ; import HeapBundle from './c/heap' ; import IoBundle from './io/index' ; import ViewsBundle from './views/index' ; import ArduinoBundle , { ArduinoPort } from './arduino' ; import PythonBundle from './python' ; import { analyseState , collectDirectives } from './c/analysis' ; import { analyseSkulptState , getSkulptSuspensionsCopy , SkulptAnalysis } from "./python/analysis/analysis" ; import { Directive , parseDirectives } from "./python/directives" ; import { ActionTypes as StepperActionTypes , ActionTypes , stepperDisplayError , stepperExecutionError , stepperExecutionSuccess } from "./actionTypes" ; import { ActionTypes as CommonActionTypes } from "../common/actionTypes" ; import { ActionTypes as BufferActionTypes } from "../buffers/actionTypes" ; import { ActionTypes as RecorderActionTypes } from "../recorder/actionTypes" ; import { ActionTypes as AppActionTypes } from "../actionTypes" ; import { getCurrentStepperState , getStepper , isStepperInterrupting } from "./selectors" ; import { AppStore , AppStoreReplay , CodecastPlatform } from "../store" ; import { TermBuffer } from "./io/terminal" ; import { PlayerInstant } from "../player" ; import { ReplayContext } from "../player/sagas" ; import { Bundle } from "../linker" ; import { App } from "../index" ; import { quickAlgoLibraries } from "../task/libs/quickalgo_librairies" ; import { taskResetDone } from "../task/task_slice" ; import { ActionTypes as PlayerActionTypes } from "../player/actionTypes" ; import { getCurrentImmerState } from "../task/utils" ; import PythonInterpreter from "./python/python_interpreter" ; export enum StepperStepMode { Run = 'run' , Expr = 'expr' , Into = 'into' , Out = 'out' , Over = 'over' } export enum StepperControlsType { Normal = 'normal' , StepByStep = 'step_by_step' } export interface StepperDirectives { ordered : readonly Directive [ ] ; functionCallStack : any ; functionCallStackMap : any ; } export const initialStepperStateControls = { hide : false , fullView : false , stack : { focusDepth : 0 , } , hPan : 0 , vPan : 0 , cellPan : 0 } ; let currentStepperTask ; /**
 * TODO: This type is used in directives but it may actually be a different type that is used there.
 */ export type StepperControls = typeof initialStepperStateControls ; const initialStateStepperState = { platform : 'unix' as CodecastPlatform , input : '' , output : '' , terminal : null as TermBuffer , suspensions : [ ] as readonly any [ ] , programState : { } as any , lastProgramState : { } as any , ports : [ ] as ArduinoPort [ ] , selectedPort : { } as any , controls : initialStepperStateControls , analysis : null as SkulptAnalysis | any , lastAnalysis : null as SkulptAnalysis , directives : { ordered : [ ] , functionCallStack : null , functionCallStackMap : null } as StepperDirectives , inputBuffer : '' , error : '' , serial : { speed : false } , options : { memorySize : 0x10000 , stackSize : 4096 } , isFinished : false , contextState : { } as any , } ; export type StepperState = typeof initialStateStepperState ; export enum StepperStatus { Clear = 'clear' , Idle = 'idle' , Starting = 'starting' , Running = 'running' } export const initialStateStepper = { status : StepperStatus . Clear , speed : 0 , undo : [ ] , redo : [ ] , initialStepperState : initialStateStepperState , currentStepperState : null as StepperState , interrupting : false , mode : null as StepperStepMode , options : { } as any , controls : StepperControlsType . Normal , synchronizingAnalysis : false , error : null as any , } ; export type Stepper = typeof initialStateStepper ; function initReducer ( state ) { state . stepper = { ... initialStateStepper } ; } export default function ( bundle ) { bundle . addReducer ( AppActionTypes . AppInit , initReducer ) ; bundle . defineAction ( ActionTypes . StepperTaskCancelled ) ; bundle . addReducer ( ActionTypes . StepperTaskCancelled , stepperTaskCancelledReducer ) ; bundle . defineAction ( ActionTypes . StepperRestart ) ; bundle . addReducer ( ActionTypes . StepperRestart , stepperRestartReducer ) ; bundle . defineAction ( ActionTypes . StepperReset ) ; bundle . addReducer ( ActionTypes . StepperReset , stepperResetReducer ) ; bundle . defineAction ( ActionTypes . StepperStep ) ; bundle . addReducer ( ActionTypes . StepperStep , stepperStepReducer ) ; bundle . defineAction ( ActionTypes . StepperStarted ) ; bundle . addReducer ( ActionTypes . StepperStarted , stepperStartedReducer ) ; bundle . defineAction ( ActionTypes . StepperInteract ) ; bundle . defineAction ( ActionTypes . StepperInteractBefore ) ; bundle . defineAction ( ActionTypes . StepperProgress ) ; bundle . addReducer ( ActionTypes . StepperProgress , stepperProgressReducer ) ; bundle . defineAction ( ActionTypes . StepperIdle ) ; bundle . addReducer ( ActionTypes . StepperIdle , stepperIdleReducer ) ; bundle . defineAction ( ActionTypes . StepperExit ) ; bundle . addReducer ( ActionTypes . StepperExit , stepperExitReducer ) ; bundle . defineAction ( ActionTypes . StepperInterrupting ) ; bundle . addReducer ( ActionTypes . StepperInterrupting , stepperInterruptReducer ) ; bundle . defineAction ( ActionTypes . StepperInterrupt ) ; bundle . addReducer ( ActionTypes . StepperInterrupt , stepperInterruptReducer ) ; bundle . defineAction ( ActionTypes . StepperInterrupted ) ; bundle . addReducer ( ActionTypes . StepperInterrupted , stepperInterruptedReducer ) ; bundle . defineAction ( ActionTypes . StepperUndo ) ; bundle . addReducer ( ActionTypes . StepperUndo , stepperUndoReducer ) ; bundle . defineAction ( ActionTypes . StepperRedo ) ; bundle . addReducer ( ActionTypes . StepperRedo , stepperRedoReducer ) ; bundle . defineAction ( ActionTypes . StepperConfigure ) ; bundle . addReducer ( ActionTypes . StepperConfigure , stepperConfigureReducer ) ; bundle . defineAction ( ActionTypes . StepperSpeedChanged ) ; bundle . addReducer ( ActionTypes . StepperSpeedChanged , stepperSpeedChangedReducer ) ; bundle . defineAction ( ActionTypes . StepperControlsChanged ) ; bundle . addReducer ( ActionTypes . StepperControlsChanged , stepperControlsChangedReducer ) ; bundle . defineAction ( ActionTypes . StepperStackUp ) ; bundle . addReducer ( ActionTypes . StepperStackUp , stepperStackUpReducer ) ; bundle . defineAction ( ActionTypes . StepperStackDown ) ; bundle . addReducer ( ActionTypes . StepperStackDown , stepperStackDownReducer ) ; bundle . defineAction ( ActionTypes . StepperViewControlsChanged ) ; bundle . addReducer ( ActionTypes . StepperViewControlsChanged , stepperViewControlsChangedReducer ) ; bundle . defineAction ( ActionTypes . StepperSynchronizingAnalysisChanged ) ; bundle . addReducer ( ActionTypes . StepperSynchronizingAnalysisChanged , stepperSynchronizingAnalysisChangedReducer ) ; bundle . defineAction ( ActionTypes . StepperExecutionSuccess ) ; bundle . addReducer ( ActionTypes . StepperExecutionSuccess , stepperExitReducer ) ; bundle . defineAction ( ActionTypes . StepperExecutionError ) ; bundle . addReducer ( ActionTypes . StepperExecutionError , stepperExecutionErrorReducer ) ; bundle . defineAction ( ActionTypes . StepperDisplayError ) ; bundle . addReducer ( ActionTypes . StepperDisplayError , stepperDisplayErrorReducer ) ; bundle . defineAction ( ActionTypes . StepperClearError ) ; bundle . addReducer ( ActionTypes . StepperClearError , stepperClearErrorReducer ) ; bundle . defineAction ( ActionTypes . StepperEnabled ) ; bundle . defineAction ( ActionTypes . StepperDisabled ) ; bundle . addSaga ( stepperSaga ) ; bundle . defer ( postLink ) ; bundle . include ( ApiBundle ) ; bundle . include ( CompileBundle ) ; bundle . include ( EffectsBundle ) ; bundle . include ( DelayBundle ) ; bundle . include ( HeapBundle ) ; bundle . include ( IoBundle ) ; bundle . include ( ViewsBundle ) ; bundle . include ( ArduinoBundle ) ; bundle . include ( PythonBundle ) ; } ; param stepperState param { string } context param stepperContext function enrichStepperState ( stepperState , context , stepperContext = null ) { const { programState , controls } = stepperState ; if ( ! programState ) { return ; } if ( stepperState . platform === 'python' ) { if ( context === 'Stepper.Progress' ) { if ( window . currentPythonRunner . _isFinished ) { stepperState . isFinished = true ; } else { console . log ( 'INCREASE STEP NUM TO ' , stepperState . analysis . stepNum + 1 ) ; stepperState . analysis = analyseSkulptState ( stepperState . suspensions , stepperState . lastAnalysis , stepperState . analysis . stepNum + 1 ) ; stepperState . directives = { ordered : parseDirectives ( stepperState . analysis ) , functionCallStackMap : null , functionCallStack : null } ; } } if ( ! stepperState . analysis ) { stepperState . analysis = { functionCallStack : [ ] , code : window . currentPythonRunner . _code , lines : window . currentPythonRunner . _code . split ( "\n" ) , stepNum : 0 } ; stepperState . lastAnalysis = { functionCallStack : [ ] , code : window . currentPythonRunner . _code , lines : window . currentPythonRunner . _code . split ( "\n" ) , stepNum : 0 } ; } } else { const analysis = stepperState . analysis = analyseState ( programState ) ; const focusDepth = controls . stack . focusDepth ; stepperState . directives = collectDirectives ( analysis . functionCallStack , focusDepth ) ; } } export function clearStepper ( stepper ) { stepper . status = StepperStatus . Clear ; stepper . undo = [ ] ; stepper . redo = [ ] ; stepper . interrupting = false ; stepper . initialStepperState = null ; stepper . currentStepperState = null ; } export function getNodeRange ( stepperState ? ) { if ( ! stepperState ) { return null ; } if ( stepperState . platform === 'python' ) { const { functionCallStack } = stepperState . analysis ; const stackFrame = functionCallStack [ 0 ] ; if ( ! stackFrame ) { return null ; } const line = stackFrame . currentLine ; const columnNumber = stackFrame . currentColumn ; return { start : { row : ( line - 1 ) , column : columnNumber , } , end : { row : ( line - 1 ) , column : 100 , } } ; } else { const { control } = stepperState . programState ; if ( ! control || ! control . node ) { return null ; } const focusDepth = stepperState . controls . stack . focusDepth ; if ( focusDepth === 0 ) { return control . node [ 1 ] . range ; } else { const { functionCallStack } = stepperState . analysis ; const stackFrame = functionCallStack [ functionCallStack . length - focusDepth ] ; return stackFrame . scope . cont . node [ 1 ] . range ; } } } function stringifyError ( error ) { if ( process . env [ 'NODE_ENV' ] === 'production' ) { return error . toString ( ) ; } if ( error && error . stack ) { return error . stack . toString ( ) ; } return JSON . stringify ( error ) ; } function stepperRestartReducer ( state , { payload : { stepperState } } ) { const { platform } = state . options ; if ( stepperState ) { enrichStepperState ( stepperState , 'Stepper.Restart' ) ; /**
         * StepperState comes from an action so it's not an immer draft.
         */ stepperState = { ... stepperState } ; } else { if ( platform === 'python' ) { stepperState = state . stepper . initialStepperState ; const source = state . buffers [ 'source' ] . model . document . toString ( ) ; type { string } const pythonSource = source + "\npass" ; window . currentPythonRunner . initCodes ( [ pythonSource ] ) ; } else { stepperState = state . stepper . initialStepperState ; } } state . stepper . status = StepperStatus . Idle ; state . stepper . initialStepperState = stepperState ; state . stepper . currentStepperState = stepperState ; state . stepper . redo = [ ] ; state . task . resetDone = false ; state . task . inputs = [ ] ; } function stepperTaskCancelledReducer ( ) { currentStepperTask = null ; } function stepperResetReducer ( state , { payload : { stepperState } } ) { state . stepper = Object . freeze ( stepperState ) ; } function stepperStepReducer ( state ) { state . stepper . status = StepperStatus . Starting ; state . stepper . interrupting = false ; } function stepperStartedReducer ( state , action ) { state . stepper . status = StepperStatus . Running ; state . stepper . mode = action . mode ; state . stepper . redo = [ ] ; state . stepper . undo . unshift ( state . stepper . currentStepperState ) ; } function stepperProgressReducer ( state , { payload : { stepperContext , progress } } ) { /**
     * TODO: stepperState comes from an action so it's not an immer draft.
     */ stepperContext . state = { ... stepperContext . state } ; if ( false !== progress ) { if ( stepperContext . state . platform === 'python' ) { stepperContext . state . suspensions = getSkulptSuspensionsCopy ( window . currentPythonRunner . _debugger . suspension_stack ) ; } enrichStepperState ( stepperContext . state , 'Stepper.Progress' , stepperContext ) ; } const context = quickAlgoLibraries . getContext ( null , state . environment ) ; if ( context && context . getInnerState ) { const contextState = context . getInnerState ( ) ; state . task . state = getCurrentImmerState ( contextState ) ; } state . stepper . currentStepperState = stepperContext . state ; if ( state . compile . status === CompileStatus . Error ) { state . stepper . currentStepperState . isFinished = false ; } } function stepperIdleReducer ( state , { payload : { stepperContext } } ) { enrichStepperState ( stepperContext . state , 'Stepper.Idle' , stepperContext ) ; /**
     * TODO: stepperState comes from an action so it's not an immer draft.
     */ stepperContext . state = { ... stepperContext . state } ; state . stepper . currentStepperState = stepperContext . state ; state . stepper . status = StepperStatus . Idle ; state . stepper . mode = null ; } export function stepperExitReducer ( state ) { clearStepper ( state . stepper ) ; state . task . inputNeeded = false ; } function stepperInterruptReducer ( state ) { if ( state . stepper . status !== StepperStatus . Idle ) { state . stepper . interrupting = true ; } } function stepperInterruptedReducer ( state ) { state . stepper . interrupting = false ; } function stepperUndoReducer ( state ) { const undo = state . stepper . undo ; if ( undo . length ) { const currentStepperState = state . stepper . currentStepperState ; state . stepper . currentStepperState = undo . shift ( ) ; state . stepper . redo . unshift ( currentStepperState ) ; } } function stepperRedoReducer ( state ) { const redo = state . stepper . redo ; if ( redo . length ) { const currentStepperState = state . stepper . currentStepperState ; state . stepper . currentStepperState = redo . shift ( ) ; state . stepper . undo . unshift ( currentStepperState ) ; } } function stepperConfigureReducer ( state , action ) { const { options } = action ; return state . stepper . options = options ; } function stepperSpeedChangedReducer ( state , { payload : { speed } } ) { return state . stepper . speed = speed ; } function stepperControlsChangedReducer ( state , { payload : { controls } } ) { return state . stepper . controls = controls ; } function stepperStackUpReducer ( state ) { let { controls , analysis } = state . stepper . currentStepperState ; let focusDepth = controls . stack . focusDepth ; if ( focusDepth > 0 ) { focusDepth -= 1 ; controls . stack . focusDepth = focusDepth ; const directives = collectDirectives ( analysis . functionCallStack , focusDepth ) ; state . stepper . currentStepperState . controls = controls ; state . stepper . currentStepperState . directives = directives ; } } function stepperStackDownReducer ( state ) { let { controls , analysis } = state . stepper . currentStepperState ; const stackDepth = analysis . functionCallStack . length ; let focusDepth = controls . stack . focusDepth ; if ( focusDepth + 1 < stackDepth ) { focusDepth += 1 ; controls . stack . focusDepth = focusDepth ; const directives = collectDirectives ( analysis . functionCallStack , focusDepth ) ; state . stepper . currentStepperState . controls = controls ; state . stepper . currentStepperState . directives = directives ; } } function stepperViewControlsChangedReducer ( state , action ) { const { key , update } = action ; let { controls } = state . stepper . currentStepperState ; if ( controls [ key ] ) { Object . keys ( update ) . forEach ( function ( name ) { controls [ key ] [ name ] = update [ name ] ; } ) ; } else { controls [ key ] = update ; } } function stepperSynchronizingAnalysisChangedReducer ( state , { payload } ) { state . stepper . synchronizingAnalysis = payload ; } export function stepperExecutionErrorReducer ( state ) { clearStepper ( state . stepper ) ; } export function stepperDisplayErrorReducer ( state , { payload } ) { state . stepper . error = payload . error ; } function stepperClearErrorReducer ( state ) { state . stepper . error = null ; } function * compileSucceededSaga ( app ) { try { yield put ( { type : ActionTypes . StepperDisabled } ) ; let state = yield select ( ) ; let stepperState = yield call ( app . stepperApi . buildState , state , app . environment ) ; console . log ( '[stepper init] current state' , state . task . state , 'context state' , stepperState . contextState ) ; const newState = yield select ( ) ; console . log ( '[stepper init] new state' , newState . task . state ) ; state = yield select ( ) ; if ( state . compile . status !== 'error' ) { yield put ( { type : ActionTypes . StepperEnabled } ) ; yield put ( { type : ActionTypes . StepperRestart , payload : { stepperState } } ) ; } } catch ( error ) { yield put ( { type : CommonActionTypes . Error , payload : { source : 'stepper' , error } } ) ; console . error ( error ) ; } } function * recorderStoppingSaga ( ) { yield put ( { type : ActionTypes . StepperInterrupt } ) ; yield put ( { type : ActionTypes . StepperExit } ) ; } function * stepperEnabledSaga ( app ) { currentStepperTask = yield fork ( app . stepperApi . rootStepperSaga , app ) ; } export function * stepperDisabledSaga ( action , leaveContext = false ) { const oldTask = currentStepperTask ; console . log ( 'try to disable stepper' , oldTask , leaveContext ) ; if ( leaveContext ) { yield put ( taskResetDone ( false ) ) ; } if ( oldTask ) { yield cancel ( oldTask ) ; yield put ( { type : ActionTypes . StepperTaskCancelled } ) ; } const startPos = { row : 0 , column : 0 } ; yield put ( { type : BufferActionTypes . BufferHighlight , buffer : 'source' , range : { start : startPos , end : startPos } } ) ; } function * stepperInteractBeforeSaga ( app , { meta : { resolve , reject } } ) { let state = yield select ( ) ; console . log ( 'inside stepper interact before' ) ; if ( isStepperInterrupting ( state ) || StepperStatus . Clear === state . stepper . status ) { console . log ( 'stepper is still interrupting' ) ; yield call ( reject , new StepperError ( 'interrupt' , 'interrupted' ) ) ; return ; } yield call ( resolve , true ) ; } function * stepperInteractSaga ( app , { payload : { stepperContext , arg } , meta : { resolve , reject } } ) { let state = yield select ( ) ; if ( ! state . stepper . synchronizingAnalysis ) { yield put ( { type : ActionTypes . StepperProgress , payload : { stepperContext , progress : arg . progress } } ) ; if ( isStepperInterrupting ( state ) || StepperStatus . Clear === state . stepper . status ) { console . log ( 'stepper is still interrupting' ) ; yield call ( reject , new StepperError ( 'interrupt' , 'interrupted' ) ) ; return ; } } const saga = arg . saga || ( 'main' === stepperContext . environment ? stepperWaitSaga : null ) ; let completed = true ; let interrupted = false ; if ( saga ) { const result = yield race ( { completed : call ( saga , stepperContext ) , interrupted : take ( ActionTypes . StepperInterrupt ) } ) ; completed = result . completed ; interrupted = result . interrupted ; } if ( state . stepper . synchronizingAnalysis ) { yield call ( resolve , completed ) ; return ; } state = yield select ( ) ; stepperContext . state = { ... getCurrentStepperState ( state ) } ; const context = quickAlgoLibraries . getContext ( null , state . environment ) ; if ( null !== stepperContext . speed ) { stepperContext . speed = getStepper ( state ) . speed ; if ( context && context . changeDelay ) { context . changeDelay ( 255 - stepperContext . speed ) ; } } else { if ( context && context . changeDelay ) { context . changeDelay ( 0 ) ; } } if ( interrupted ) { /**
         * It is strange to use reject() for a behavior that is normal (pausing the stepper).
         */ yield call ( reject , new StepperError ( 'interrupt' , 'interrupted' ) ) ; } else { yield call ( resolve , completed ) ; } } function * stepperWaitSaga ( ) { console . log ( 'stepper wait' ) ; yield delay ( 0 ) ; } function * stepperInterruptSaga ( app ) { const state = yield select ( ) ; const curStepperState = getCurrentStepperState ( state ) ; if ( ! curStepperState ) { return ; } const stepperContext = createStepperContext ( getStepper ( state ) , { dispatch : app . dispatch , environment : app . environment } ) ; if ( stepperContext . state . platform === 'python' ) { yield call ( stepperPythonRunFromBeginningIfNecessary , stepperContext ) ; } yield put ( { type : ActionTypes . StepperIdle , payload : { stepperContext } } ) ; } function createStepperContext ( stepper , { dispatch , waitForProgress , quickAlgoCallsLogger , environment , speed , executeEffects } ) { let stepperContext = makeContext ( stepper , { interactBefore : ( arg ) => { return new Promise ( ( resolve , reject ) => { dispatch ( { type : ActionTypes . StepperInteractBefore , payload : { stepperContext , arg } , meta : { resolve , reject } } ) ; } ) ; } , interactAfter : ( arg ) => { return new Promise ( ( resolve , reject ) => { dispatch ( { type : ActionTypes . StepperInteract , payload : { stepperContext , arg } , meta : { resolve , reject } } ) ; } ) ; } , waitForProgress , quickAlgoCallsLogger , dispatch , environment , speed , executeEffects , } ) ; return stepperContext ; } function * stepperStepSaga ( app , action ) { let stepperContext ; try { const state = yield select ( ) ; const { waitForProgress , quickAlgoCallsLogger } = action . payload ; const stepper = getStepper ( state ) ; if ( stepper . status === StepperStatus . Starting ) { yield put ( { type : ActionTypes . StepperStarted , mode : action . payload . mode } ) ; console . log ( 'execution speed' , action . payload . useSpeed ? stepper . speed : null ) ; stepperContext = createStepperContext ( stepper , { dispatch : app . dispatch , waitForProgress , quickAlgoCallsLogger , environment : state . environment , speed : action . payload . useSpeed && ! action . payload . immediate ? stepper . speed : null , executeEffects : app . stepperApi . executeEffects , } ) ; console . log ( 'execution stepper context' , stepperContext ) ; if ( action . payload . setStepperContext ) { action . payload . setStepperContext ( stepperContext ) ; } console . log ( '[stepper.step] Creating new stepper context' , stepperContext , stepperContext . resume , state . environment , stepperContext . environment ) ; if ( stepperContext . state . platform === 'python' ) { yield call ( stepperPythonRunFromBeginningIfNecessary , stepperContext ) ; } try { yield call ( performStep , stepperContext , action . payload . mode ) ; } catch ( ex ) { console . log ( 'stepperStepSaga has catched' , ex ) ; if ( ! ( ex instanceof StepperError ) ) { ex = new StepperError ( 'error' , stringifyError ( ex ) ) ; } if ( ex . condition === 'interrupt' ) { stepperContext . interrupted = true ; yield put ( { type : ActionTypes . StepperInterrupted } ) ; } if ( ex . condition === 'error' ) { yield put ( stepperExecutionError ( ex . message ) ) ; } } if ( stepperContext . state . platform === 'python' ) { stepperContext . state . suspensions = getSkulptSuspensionsCopy ( window . currentPythonRunner . _debugger . suspension_stack ) ; } else if ( stepperContext . state . platform === 'unix' ) { stepperContext . state . isFinished = ! stepperContext . state . programState . control ; } if ( stepperContext . state . isFinished ) { console . log ( 'check end condition' ) ; const taskContext = quickAlgoLibraries . getContext ( null , state . environment ) ; if ( taskContext && taskContext . infos . checkEndCondition ) { try { taskContext . infos . checkEndCondition ( taskContext , true ) ; } catch ( message ) { if ( taskContext . success ) { yield put ( stepperExecutionSuccess ( message ) ) ; } else { yield put ( stepperExecutionError ( message ) ) ; } } } } const newState = yield select ( ) ; const newStepper = getStepper ( newState ) ; if ( StepperStatus . Clear !== newStepper . status ) { yield put ( { type : ActionTypes . StepperIdle , payload : { stepperContext } } ) ; } } } finally { console . log ( 'end stepper saga, call onStepperDone' , stepperContext . onStepperDone ) ; if ( stepperContext . onStepperDone ) { stepperContext . onStepperDone ( stepperContext ) ; } } } /**
 * Before we do a step, we check if the state in analysis is the same as the one in the python runner.
 *
 * If it is different, it means the analysis has been overwritten by playing a record, and so
 * we need to move the python runner to the same point before we can to a step.
 */ function * stepperPythonRunFromBeginningIfNecessary ( stepperContext ) { if ( ! window . currentPythonRunner . isSynchronizedWithAnalysis ( stepperContext . state . analysis ) ) { console . log ( 'Run python from beginning is necessary' ) ; const state = yield select ( ) ; const taskContext = quickAlgoLibraries . getContext ( null , state . environment ) ; yield put ( { type : ActionTypes . StepperSynchronizingAnalysisChanged , payload : true } ) ; taskContext . display = false ; taskContext . resetAndReloadState ( state . task . currentTest , state ) ; stepperContext . state . contextState = getCurrentImmerState ( taskContext . getInnerState ( ) ) ; console . log ( 'current task state' , taskContext . getInnerState ( ) ) ; const pythonInterpreter = new PythonInterpreter ( taskContext ) ; pythonInterpreter . initCodes ( [ stepperContext . state . analysis . code ] ) ; while ( pythonInterpreter . _steps < stepperContext . state . analysis . stepNum ) { yield apply ( pythonInterpreter , pythonInterpreter . runStep , [ stepperContext . quickAlgoCallsExecutor ] ) ; if ( pythonInterpreter . _isFinished ) { break ; } } yield put ( { type : ActionTypes . StepperSynchronizingAnalysisChanged , payload : false } ) ; taskContext . display = true ; taskContext . redrawDisplay ( ) ; console . log ( 'End run python from beginning' ) ; } } function * stepperExitSaga ( ) { yield put ( { type : ActionTypes . StepperDisabled } ) ; yield put ( { type : ActionTypes . CompileClear } ) ; } function * updateSourceHighlightSaga ( ) { const state = yield select ( ) ; const stepperState = state . stepper . currentStepperState ; const range = getNodeRange ( stepperState ) ; yield put ( { type : BufferActionTypes . BufferHighlight , buffer : 'source' , range } ) ; } function * stepperSaga ( args ) { yield takeLatest ( ActionTypes . CompileSucceeded , compileSucceededSaga , args ) ; yield takeLatest ( RecorderActionTypes . RecorderStopping , recorderStoppingSaga ) ; yield takeLatest ( ActionTypes . StepperEnabled , stepperEnabledSaga , args ) ; yield takeLatest ( ActionTypes . StepperDisabled , stepperDisabledSaga ) ; yield takeLatest ( ActionTypes . StepperCompileAndStep , function * ( app , { payload } ) { const stepperState = yield select ( ( state ) => state . stepper . status ) ; if ( StepperStatus . Clear === stepperState ) { const result = yield new Promise ( ( callback ) => { app . dispatch ( { type : StepperActionTypes . CompileWait , payload : { callback , ... payload , } , } ) ; } ) ; if ( CompileStatus . Done !== result ) { return ; } } yield put ( { type : StepperActionTypes . StepperStep , payload } ) ; } , args ) ; yield takeEvery ( [ StepperActionTypes . StepperExecutionError , StepperActionTypes . CompileFailed ] , function * ( { payload } ) { console . log ( 'receive an error, display it' ) ; yield put ( stepperDisplayError ( payload . error ) ) ; } ) ; } function postLink ( app ) { const { recordApi , replayApi , stepperApi } = app ; recordApi . onStart ( function * ( init ) { const state = yield select ( ) ; const stepperState = state . stepper ; if ( stepperState ) { init . speed = stepperState . speed ; } } ) ; replayApi . on ( 'start' , function * ( replayContext , event ) { const options = event [ 2 ] ; yield put ( { type : PlayerActionTypes . PlayerReset , payload : { sliceName : 'stepper' , state : { ... initialStateStepper } } } ) ; if ( options . speed ) { yield put ( { type : ActionTypes . StepperSpeedChanged , payload : { speed : options . speed } } ) ; } } ) ; replayApi . onReset ( function * ( instant ) { const stepperState = instant . state . stepper ; yield put ( { type : ActionTypes . StepperReset , payload : { stepperState } } ) ; } ) ; recordApi . on ( ActionTypes . StepperExit , function * ( addEvent ) { yield call ( addEvent , 'stepper.exit' ) ; } ) ; replayApi . on ( 'stepper.exit' , function * ( replayContext ) { yield put ( { type : ActionTypes . StepperExit } ) ; replayContext . addSaga ( function * ( ) { console . log ( 'make reset saga' ) ; const context = quickAlgoLibraries . getContext ( null , 'main' ) ; if ( context ) { const state = yield select ( ) ; context . resetAndReloadState ( state . task . currentTest , state ) ; } } ) ; } ) ; recordApi . on ( ActionTypes . StepperStarted , function * ( addEvent , action ) { const { mode } = action ; yield call ( addEvent , 'stepper.step' , mode ) ; } ) ; replayApi . on ( 'stepper.step' , function * ( replayContext , event ) { const mode = event [ 2 ] ; let promiseResolve ; const promise = new Promise ( ( resolve ) => { promiseResolve = resolve ; } ) ; const immediate = - 1 !== event . indexOf ( 'immediate' ) ; const waitForProgress = immediate ? null : ( stepperContext ) => { return new Promise ( ( cont ) => { console . log ( '[stepper.step] stepper suspend' , cont ) ; stepperSuspend ( stepperContext , cont ) ; promiseResolve ( ) ; } ) ; } ; const setStepperContext = ( stepperContext ) => { console . log ( '[stepper.step] set stepper context' , stepperContext , promiseResolve ) ; replayContext . stepperContext = stepperContext ; replayContext . stepperContext . onStepperDone = promiseResolve ; } ; console . log ( '[stepper.step] before put step' , immediate ) ; yield put ( { type : ActionTypes . StepperStep , payload : { mode , waitForProgress , immediate , setStepperContext , quickAlgoCallsLogger : replayContext . addQuickAlgoLibraryCall } } ) ; console . log ( '[stepper.step] before yield promise' , promise ) ; yield promise ; console . log ( '[stepper.step] after yield promise' , promise ) ; replayContext . addSaga ( function * ( ) { const speed = yield select ( ( state ) => state . stepper . speed ) ; console . log ( '[stepper.step] set speed' , speed ) ; const context = quickAlgoLibraries . getContext ( null , 'main' ) ; if ( context && context . changeDelay ) { context . changeDelay ( 255 - speed ) ; } } ) ; } ) ; recordApi . on ( ActionTypes . StepperInteractBefore , function * ( addEvent ) { const state = yield select ( ) ; if ( isStepperInterrupting ( state ) || StepperStatus . Clear === state . stepper . status ) { console . log ( 'stepper is still interrupting, not logging progress' ) ; return ; } yield call ( addEvent , 'stepper.progress' ) ; } ) ; replayApi . on ( 'stepper.progress' , function * ( replayContext , event ) { console . log ( '[stepper.progress] start' ) ; const immediate = - 1 !== event . indexOf ( 'immediate' ) ; const promise = new Promise ( ( resolve ) => { console . log ( '[stepper.progress] set onStepperDone' , resolve ) ; replayContext . stepperContext . waitForProgress = immediate ? null : ( stepperContext ) => { return new Promise ( ( cont ) => { console . log ( '[stepper.progress] stepper suspend' , cont ) ; stepperSuspend ( stepperContext , cont ) ; resolve ( true ) ; } ) ; } ; replayContext . stepperContext . onStepperDone = resolve ; } ) ; const { resume } = replayContext . stepperContext ; console . log ( '[stepper.progress] resume' , resume ) ; if ( resume ) { try { console . log ( '[stepper.progress] do resume' ) ; resume ( ) ; yield promise ; } catch ( e ) { console . error ( 'exception' , e ) ; } } else { console . warn ( 'There is no resume function for the stepper.progress event, skipping the event' ) ; } } ) ; recordApi . on ( ActionTypes . StepperInterrupt , function * ( addEvent ) { yield call ( addEvent , 'stepper.interrupt' ) ; } ) ; replayApi . on ( 'stepper.interrupt' , function * ( replayContext ) { const stepperContext = replayContext . stepperContext ; yield put ( { type : ActionTypes . StepperInterrupt , payload : { stepperContext } } ) ; } ) ; function stepperSuspend ( stepperContext , cont ) { console . log ( '[stepper.suspend]' ) ; stepperContext . resume = cont ; } recordApi . on ( ActionTypes . StepperUndo , function * ( addEvent ) { yield call ( addEvent , 'stepper.undo' ) ; } ) ; replayApi . on ( 'stepper.undo' , function * ( ) { yield put ( { type : ActionTypes . StepperUndo } ) ; } ) ; recordApi . on ( ActionTypes . StepperRedo , function * ( addEvent ) { yield call ( addEvent , 'stepper.redo' ) ; } ) ; replayApi . on ( 'stepper.redo' , function * ( ) { yield put ( { type : ActionTypes . StepperRedo } ) ; } ) ; recordApi . on ( ActionTypes . StepperStackUp , function * ( addEvent ) { yield call ( addEvent , 'stepper.stack.up' ) ; } ) ; replayApi . on ( 'stepper.stack.up' , function * ( ) { yield put ( { type : ActionTypes . StepperStackUp } ) ; } ) ; recordApi . on ( ActionTypes . StepperStackDown , function * ( addEvent ) { yield call ( addEvent , 'stepper.stack.down' ) ; } ) ; replayApi . on ( 'stepper.stack.down' , function * ( ) { yield put ( { type : ActionTypes . StepperStackDown } ) ; } ) ; recordApi . on ( ActionTypes . StepperViewControlsChanged , function * ( addEvent , action ) { const { key , update } = action ; yield call ( addEvent , 'stepper.view.update' , key , update ) ; } ) ; replayApi . on ( 'stepper.view.update' , function * ( replayContext , event ) { const key = event [ 2 ] ; const update = event [ 3 ] ; yield put ( { type : ActionTypes . StepperViewControlsChanged , key , update } ) ; } ) ; recordApi . on ( ActionTypes . StepperSpeedChanged , function * ( addEvent , action ) { const { payload : { speed } } = action ; yield call ( addEvent , 'stepper.speed.changed' , speed ) ; } ) ; replayApi . on ( 'stepper.speed.changed' , function * ( replayContext , event ) { const speed = event [ 2 ] ; yield put ( { type : ActionTypes . StepperSpeedChanged , payload : { speed } } ) ; replayContext . addSaga ( function * ( ) { const context = quickAlgoLibraries . getContext ( null , 'main' ) ; if ( context && context . changeDelay ) { context . changeDelay ( 255 - speed ) ; } } ) ; } ) ; recordApi . on ( ActionTypes . StepperControlsChanged , function * ( addEvent , action ) { const { payload : { controls } } = action ; yield call ( addEvent , 'stepper.controls.changed' , controls ) ; } ) ; replayApi . on ( 'stepper.controls.changed' , function * ( replayContext , event ) { const controls = event [ 2 ] ; yield put ( { type : ActionTypes . StepperControlsChanged , payload : { controls } } ) ; } ) ; recordApi . on ( ActionTypes . StepperClearError , function * ( addEvent ) { yield call ( addEvent , 'stepper.clear_error' ) ; } ) ; replayApi . on ( [ 'compile.clearDiagnostics' , 'stepper.clear_error' ] , function * ( ) { yield put ( { type : ActionTypes . StepperClearError } ) ; } ) ; stepperApi . onInit ( function ( stepperState , state ) { const { platform } = state . options ; switch ( platform ) { case 'python' : stepperState . lastProgramState = { } ; stepperState . programState = { ... stepperState . lastProgramState } ; break ; default : const syntaxTree = state . compile . syntaxTree ; const options = stepperState . options = { memorySize : 0x10000 , stackSize : 4096 , } ; const emptyProgramState = stepperState . lastProgramState = C . makeCore ( options . memorySize ) ; const initialProgramState = stepperState . programState = { ... emptyProgramState } ; const decls = syntaxTree [ 2 ] ; C . execDecls ( initialProgramState , decls ) ; C . setupCall ( initialProgramState , 'main' ) ; break ; } } ) ; stepperApi . addSaga ( function * mainStepperSaga ( args ) { yield takeEvery ( ActionTypes . StepperInteract , stepperInteractSaga , args ) ; yield takeEvery ( ActionTypes . StepperInteractBefore , stepperInteractBeforeSaga , args ) ; yield takeEvery ( ActionTypes . StepperStep , stepperStepSaga , args ) ; yield takeEvery ( ActionTypes . StepperInterrupt , stepperInterruptSaga , args ) ; yield takeEvery ( ActionTypes . StepperExit , stepperExitSaga ) ; yield takeEvery ( [ StepperActionTypes . StepperExecutionSuccess , StepperActionTypes . StepperExecutionError , StepperActionTypes . CompileFailed , ] , function * ( ) { let state = yield select ( ) ; if ( state . stepper && state . stepper . status === StepperStatus . Running && ! isStepperInterrupting ( state ) ) { yield put ( { type : ActionTypes . StepperInterrupt , payload : { } } ) ; } yield call ( stepperDisabledSaga , true ) ; } ) ; yield takeLatest ( [ ActionTypes . StepperProgress , ActionTypes . StepperIdle , ActionTypes . StepperRestart , ActionTypes . StepperUndo , ActionTypes . StepperRedo , ActionTypes . StepperStackUp , ActionTypes . StepperStackDown ] , updateSourceHighlightSaga ) ; } ) ; }