import * as C from '@france-ioi/persistent-c' ; import { all , call , select , take } from 'redux-saga/effects' ; import { clearLoadedReferences } from "./python/analysis/analysis" ; import { AppStore , AppStoreReplay } from "../store" ; import { initialStepperStateControls , Stepper , StepperState } from "./index" ; import { Bundle } from "../linker" ; import { quickAlgoLibraries } from "../task/libs/quickalgo_librairies" ; import { createDraft } from "immer" ; import { getCurrentImmerState } from "../task/utils" ; import { ActionTypes as CompileActionTypes } from "./actionTypes" ; export interface QuickalgoLibraryCall { module : string ; action : string ; args : any [ ] ; } export interface StepperContext { state ? : StepperState ; interrupted ? : boolean ; interactBefore ? : Function ; interactAfter : Function ; waitForProgress ? : Function ; onStepperDone ? : Function ; dispatch ? : Function ; quickAlgoCallsLogger ? : Function ; quickAlgoCallsExecutor ? : Function ; resume ? : Function | null ; position ? : any ; lineCounter ? : number ; speed ? : number ; unixNextStepCondition ? : 0 ; makeDelay ? : boolean ; quickAlgoContext ? : any ; environment ? : string ; executeEffects ? : Function ; } export interface StepperContextParameters { interactBefore ? : Function ; interactAfter ? : Function ; waitForProgress ? : Function ; onStepperDone ? : Function ; dispatch ? : Function ; quickAlgoCallsLogger ? : Function ; environment ? : string ; speed ? : number ; executeEffects ? : Function ; } const delay = delay => new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ; export interface StepperApi { onInit ? : Function ; addSaga ? : Function ; onEffect ? : Function ; addBuiltin ? : Function ; buildState ? : any ; rootStepperSaga ? : any ; executeEffects ? : Function ; } export default function ( bundle ) { const stepperApi = { onInit , addSaga , onEffect , addBuiltin , buildState , rootStepperSaga , executeEffects , } ; const initCallbacks = [ ] ; const stepperSagas = [ ] ; const effectHandlers = new Map ( ) ; const builtinHandlers = new Map ( ) ; function onInit ( callback ) { initCallbacks . push ( callback ) ; } function addSaga ( saga ) { stepperSagas . push ( saga ) ; } function * rootStepperSaga ( ... args ) { yield all ( stepperSagas . map ( saga => call ( saga , ... args ) ) ) ; } function onEffect ( name , handler ) { effectHandlers . set ( name , handler ) ; } function addBuiltin ( name , handler ) { builtinHandlers . set ( name , handler ) ; } async function buildState ( state , environment ) { const { platform } = state . options ; const curStepperState = { platform } as StepperState ; for ( let callback of initCallbacks ) { callback ( curStepperState , state , environment ) ; } console . log ( 'do build state' ) ; const stepper = { ... state . stepper , currentStepperState : curStepperState , } ; const stepperContext = makeContext ( stepper , { interactBefore , interactAfter , environment , executeEffects , } ) ; if ( stepperContext . state . platform === 'python' ) { return stepperContext . state ; } else { while ( ! inUserCode ( stepperContext . state ) ) { const effects = C . step ( stepperContext . state . programState ) ; if ( effects ) { await executeEffects ( stepperContext , effects [ Symbol . iterator ] ( ) ) ; } } return stepperContext . state ; } function interactBefore ( ) { return Promise . resolve ( true ) ; } function interactAfter ( { saga } ) { return new Promise ( ( resolve , reject ) => { if ( saga ) { return reject ( new StepperError ( 'error' , 'cannot interact in buildState' ) ) ; } resolve ( true ) ; } ) ; } } async function executeEffects ( stepperContext , iterator ) { let lastResult ; while ( true ) { const { done , value } = iterator . next ( lastResult ) ; if ( done ) { return value ; } const name = value [ 0 ] ; if ( name === 'interact' ) { lastResult = await stepperContext . interactAfter ( value [ 1 ] || { } ) ; } else if ( name === 'promise' ) { console . log ( 'await promise' ) ; lastResult = await value [ 1 ] ; console . log ( 'promise result' , lastResult ) ; } else if ( name === 'builtin' ) { const builtin = value [ 1 ] ; if ( ! builtinHandlers . has ( builtin ) ) { throw new StepperError ( 'error' , `unknown builtin ${ builtin }` ) ; } lastResult = await executeEffects ( stepperContext , builtinHandlers . get ( builtin ) ( stepperContext , ... value . slice ( 2 ) ) ) ; } else { if ( ! effectHandlers . has ( name ) ) { throw new StepperError ( 'error' , `unhandled effect ${ name }` ) ; } lastResult = await executeEffects ( stepperContext , effectHandlers . get ( name ) ( stepperContext , ... value . slice ( 1 ) ) ) ; } } } bundle . defineValue ( 'stepperApi' , stepperApi ) ; } function getNodeStartRow ( stepperState ) { if ( ! stepperState ) { return undefined ; } const { control } = stepperState . programState ; if ( ! control || ! control . node ) { return undefined ; } const { range } = control . node [ 1 ] ; return range && range . start . row ; } export function makeContext ( stepper , { interactBefore , interactAfter , waitForProgress , dispatch , quickAlgoCallsLogger , environment , speed , executeEffects } ) { /**
     * We create a new state object here instead of mutating the state. This is intended.
     */ const state = stepper . currentStepperState ; const stepperContext = { interactBefore : interactBefore ? interactBefore : ( ) => { return Promise . resolve ( true ) ; } , interactAfter : interactAfter ? interactAfter : ( ) => { return Promise . resolve ( true ) ; } , waitForProgress , dispatch , quickAlgoCallsLogger , resume : null , position : getNodeStartRow ( state ) , lineCounter : 0 , speed : undefined !== speed ? speed : stepper . speed , unixNextStepCondition : 0 , state : { ... state , } , quickAlgoContext : quickAlgoLibraries . getContext ( null , environment ) , environment , executeEffects , } ; stepperContext . quickAlgoCallsExecutor = createQuickAlgoLibraryExecutor ( stepperContext ) ; if ( state . platform === 'python' ) { return { ... stepperContext , state : { ... stepperContext . state , ... ( state . analysis ? { lastAnalysis : Object . freeze ( clearLoadedReferences ( state . analysis ) ) } : { } ) , controls : resetControls ( state . controls ) , } , } ; } else { return { ... stepperContext , state : { ... stepperContext . state , programState : C . clearMemoryLog ( state . programState ) , lastProgramState : { ... state . programState } , controls : resetControls ( state . controls ) , } , } ; } } function resetControls ( controls ) { return initialStepperStateControls ; } async function executeSingleStep ( stepperContext ) { if ( isStuck ( stepperContext . state ) ) { throw new StepperError ( 'stuck' , 'execution cannot proceed' ) ; } await stepperContext . interactBefore ( ) ; if ( stepperContext . waitForProgress ) { console . log ( 'wait for progress' ) ; await stepperContext . waitForProgress ( stepperContext ) ; console . log ( 'end wait for progress, continuing' ) ; } if ( stepperContext . state . platform === 'python' ) { const result = await window . currentPythonRunner . runStep ( stepperContext . quickAlgoCallsExecutor ) ; console . log ( 'FINAL INTERACT' , result ) ; stepperContext . makeDelay = true ; await stepperContext . interactAfter ( { position : 0 , } ) ; console . log ( 'AFTER FINAL INTERACT' ) ; } else { const effects = C . step ( stepperContext . state . programState ) ; await stepperContext . executeEffects ( stepperContext , effects [ Symbol . iterator ] ( ) ) ; const position = getNodeStartRow ( stepperContext . state ) ; if ( 0 === stepperContext . unixNextStepCondition % 3 && C . outOfCurrentStmt ( stepperContext . state . programState ) ) { stepperContext . unixNextStepCondition ++ ; } if ( 1 === stepperContext . unixNextStepCondition % 3 && C . intoNextStmt ( stepperContext . state . programState ) ) { stepperContext . unixNextStepCondition ++ ; } if ( stepperContext . unixNextStepCondition % 3 === 2 || isStuck ( stepperContext . state ) ) { console . log ( 'do interact' ) ; stepperContext . makeDelay = true ; stepperContext . unixNextStepCondition = 0 ; await stepperContext . interactAfter ( { position } ) ; stepperContext . position = position ; } } } async function stepUntil ( stepperContext , stopCond = undefined ) { let stop = false ; let first = true ; while ( true ) { if ( isStuck ( stepperContext . state ) ) { return ; } if ( ! stop && stopCond ) { if ( stepperContext . state . platform === 'python' ) { if ( stopCond ( stepperContext . state ) ) { stop = true ; } } else { if ( stopCond ( stepperContext . state . programState ) ) { stop = true ; } } } if ( stop && inUserCode ( stepperContext . state ) ) { return ; } if ( ! first && null !== stepperContext . speed && undefined !== stepperContext . speed && stepperContext . speed < 255 && stepperContext . makeDelay ) { stepperContext . makeDelay = false ; await delay ( 255 - stepperContext . speed ) ; } await executeSingleStep ( stepperContext ) ; first = false ; } } async function stepExpr ( stepperContext ) { await executeSingleStep ( stepperContext ) ; await stepUntil ( stepperContext , C . intoNextExpr ) ; } async function stepInto ( stepperContext ) { await executeSingleStep ( stepperContext ) ; if ( stepperContext . state . platform === 'unix' || stepperContext . state . platform === 'arduino' ) { await stepUntil ( stepperContext , C . outOfCurrentStmt ) ; await stepUntil ( stepperContext , C . intoNextStmt ) ; } } async function stepOut ( stepperContext ) { if ( ! isStuck ( stepperContext . state ) ) { if ( stepperContext . state . platform === 'python' ) { const nbSuspensions = stepperContext . state . suspensions . length ; await executeSingleStep ( stepperContext ) ; await stepUntil ( stepperContext , curState => { console . log ( curState . suspensions . length , nbSuspensions ) ; return ( curState . suspensions . length < nbSuspensions ) ; } ) ; } else { const refScope = stepperContext . state . programState . scope ; const funcScope = C . findClosestFunctionScope ( refScope ) ; await stepUntil ( stepperContext , programState => programState . scope === funcScope . parent ) ; } } return stepperContext ; } async function stepOver ( stepperContext ) { if ( stepperContext . state . platform === 'python' ) { if ( stepperContext . state . suspensions ) { const nbSuspensions = stepperContext . state . suspensions . length ; await executeSingleStep ( stepperContext ) ; await stepUntil ( stepperContext , curState => { return ( curState . suspensions . length <= nbSuspensions ) ; } ) ; } else { await executeSingleStep ( stepperContext ) ; } } else { const refCurrentScope = stepperContext . state . programState . scope ; await executeSingleStep ( stepperContext ) ; await stepUntil ( stepperContext , programState => C . outOfCurrentStmt ( programState ) && C . notInNestedCall ( programState . scope , refCurrentScope ) ) ; await stepUntil ( stepperContext , C . intoNextStmt ) ; } } export async function performStep ( stepperContext , mode ) { switch ( mode ) { case 'run' : await stepUntil ( stepperContext ) ; break ; case 'into' : await stepInto ( stepperContext ) ; break ; case 'expr' : await stepExpr ( stepperContext ) ; break ; case 'out' : await stepOut ( stepperContext ) ; break ; case 'over' : await stepOver ( stepperContext ) ; break ; } } function isStuck ( stepperState ) { if ( stepperState . platform === 'python' ) { return stepperState . isFinished ; } else { return ! stepperState . programState . control ; } } function inUserCode ( stepperState ) { if ( stepperState . platform === 'python' ) { return true ; } else { return ! ! stepperState . programState . control . node [ 1 ] . begin ; } } export function createQuickAlgoLibraryExecutor ( stepperContext , reloadState = false ) { return async ( module , action , args , callback ) => { let libraryCallResult ; const context = stepperContext . quickAlgoContext ; if ( stepperContext . state ) { console . log ( 'stepper context before' , stepperContext . state . contextState ) ; } if ( stepperContext . quickAlgoCallsLogger ) { const quickAlgoLibraryCall = { module , action , args } ; stepperContext . quickAlgoCallsLogger ( quickAlgoLibraryCall ) ; console . log ( 'LOG ACTION' , module , action , args ) ; } if ( reloadState ) { const draft = createDraft ( stepperContext . state . contextState ) ; context . reloadInnerState ( draft ) ; context . redrawDisplay ( ) ; } const makeLibraryCall = async ( ) => { let result = context [ module ] [ action ] . apply ( context , [ ... args , callback ] ) ; console . log ( 'MODULE RESULT' , result ) ; if ( ! ( Symbol . iterator in Object ( result ) ) ) { return result ; } let lastResult ; while ( true ) { const { done , value } = result . next ( ) ; console . log ( 'ITERATOR RESULT' , done , value ) ; if ( done ) { return value ; } const name = value [ 0 ] ; if ( name === 'interact' ) { console . log ( 'ASK FOR INTERACT' , stepperContext . interactAfter ) ; lastResult = await stepperContext . interactAfter ( { ... ( value [ 1 ] || { } ) , progress : false } ) ; console . log ( 'last result' , lastResult ) ; } else if ( name == 'put' ) { console . log ( 'ask put dispatch' , value [ 1 ] ) ; await stepperContext . dispatch ( value [ 1 ] ) ; } } } ; console . log ( 'before make async library call' , { module , action } ) ; try { libraryCallResult = await makeLibraryCall ( ) ; } catch ( e ) { console . log ( 'context error 2' , e ) ; await stepperContext . dispatch ( { type : CompileActionTypes . StepperInterrupting , } ) ; await stepperContext . dispatch ( { type : CompileActionTypes . StepperExecutionError , payload : { error : e , } , } ) ; } console . log ( 'after make async library call' ) ; const newStateValue = context . getInnerState ( ) ; const newState = getCurrentImmerState ( newStateValue ) ; console . log ( 'NEW LIBRARY STATE' , newState ) ; if ( stepperContext . state ) { stepperContext . state = { ... stepperContext . state , contextState : newState , } ; console . log ( 'stepper context after' , stepperContext . state . contextState ) ; } context . waitDelay ( callback , libraryCallResult ) ; return libraryCallResult ; } ; } export class StepperError extends Error { condition = null ; constructor ( condition , message ) { super ( message ) ; this . name = this . constructor . name ; this . condition = condition ; } }