import { call , put , select , takeLatest , takeEvery , take , race } from 'redux-saga/effects' ; import { asyncRequestJson } from '../utils/api' ; import { TextEncoder } from "text-encoding-utf-8" ; import { clearStepper } from "./index" ; import { ActionTypes } from "./actionTypes" ; import { ActionTypes as AppActionTypes } from "../actionTypes" ; import { getBufferModel } from "../buffers/selectors" ; import { AppStore , AppStoreReplay } from "../store" ; import { PlayerInstant } from "../player" ; import { ReplayContext } from "../player/sagas" ; import { Bundle } from "../linker" ; import { App } from "../index" ; import { checkCompilingCode } from "../task/utils" ; import { ActionTypes as PlayerActionTypes } from "../player/actionTypes" ; export enum CompileStatus { Clear = 'clear' , Running = 'running' , Done = 'done' , Error = 'error' } export const initialStateCompile = { status : CompileStatus . Clear , source : '' , syntaxTree : null as any } ; export default function ( bundle ) { function initReducer ( state ) { state . compile = { ... initialStateCompile } ; } bundle . addReducer ( AppActionTypes . AppInit , initReducer ) ; bundle . defineAction ( ActionTypes . Compile ) ; bundle . defineAction ( ActionTypes . CompileClear ) ; bundle . addReducer ( ActionTypes . CompileClear , initReducer ) ; bundle . defineAction ( ActionTypes . CompileStarted ) ; bundle . addReducer ( ActionTypes . CompileStarted , compileStartedReducer ) ; bundle . defineAction ( ActionTypes . CompileSucceeded ) ; bundle . addReducer ( ActionTypes . CompileSucceeded , compileSucceededReducer ) ; bundle . defineAction ( ActionTypes . CompileFailed ) ; bundle . addReducer ( ActionTypes . CompileFailed , compileFailedReducer ) ; bundle . defineAction ( ActionTypes . CompileWait ) ; bundle . addSaga ( function * watchCompile ( ) { yield takeLatest ( ActionTypes . Compile , function * ( ) { let state = yield select ( ) ; const sourceModel = getBufferModel ( state , 'source' ) ; const source = sourceModel . document . toString ( ) ; const { platform } = state . options ; yield put ( { type : ActionTypes . CompileStarted , source } ) ; try { checkCompilingCode ( source . trim ( ) , platform , state . environment ) ; } catch ( e ) { yield put ( { type : ActionTypes . CompileFailed , payload : { error : e , } , } ) ; return ; } let response ; if ( platform === 'python' ) { yield put ( { type : ActionTypes . CompileSucceeded , platform } ) ; } else { state = yield select ( ) ; try { const logData = state . statistics . logData ; const postData = { source , platform , logData } ; const { baseUrl } = state . options ; response = yield call ( asyncRequestJson , baseUrl + '/compile' , postData ) ; } catch ( ex ) { response = { error : ex . toString ( ) } ; } response . platform = platform ; if ( response . ast ) { yield put ( { type : ActionTypes . CompileSucceeded , response , platform } ) ; } else { yield put ( { type : ActionTypes . CompileFailed , payload : { error : { type : 'compilation' , content : response . diagnostics } } } ) ; } } } ) ; yield takeEvery ( ActionTypes . CompileWait , function * ( { payload : { callback , keepSubmission } } ) { yield put ( { type : ActionTypes . Compile , payload : { keepSubmission } } ) ; const outcome = yield race ( { [ CompileStatus . Done ] : take ( ActionTypes . StepperRestart ) , [ CompileStatus . Error ] : take ( ActionTypes . CompileFailed ) , } ) ; callback ( Object . keys ( outcome ) [ 0 ] ) ; } ) ; } ) ; bundle . defer ( function ( { recordApi , replayApi } ) { replayApi . on ( 'start' , function * ( ) { yield put ( { type : PlayerActionTypes . PlayerReset , payload : { sliceName : 'compile' , state : initialStateCompile } } ) ; } ) ; recordApi . on ( ActionTypes . CompileStarted , function * ( addEvent , action ) { const { source } = action ; yield call ( addEvent , 'compile.start' , source ) ; } ) ; replayApi . on ( [ 'stepper.compile' , 'compile.start' ] , function * ( replayContext , event ) { const source = event [ 2 ] ; yield put ( { type : ActionTypes . CompileStarted , source } ) ; } ) ; recordApi . on ( ActionTypes . CompileSucceeded , function * ( addEvent , action ) { yield call ( addEvent , 'compile.success' , action ) ; } ) ; replayApi . on ( 'compile.success' , function * ( replayContext , event ) { const action = event [ 2 ] ; yield put ( { type : ActionTypes . CompileSucceeded , ... action } ) ; } ) ; replayApi . on ( 'stepper.exit' , function * ( ) { yield put ( { type : PlayerActionTypes . PlayerReset , payload : { sliceName : 'compile' , state : initialStateCompile } } ) ; } ) ; replayApi . onReset ( function * ( instant ) { const compileModel = instant . state . compile ; yield put ( { type : PlayerActionTypes . PlayerReset , payload : { sliceName : 'compile' , state : compileModel } } ) ; } ) ; } ) ; } ; const addNodeRanges = function ( source , syntaxTree ) { const encoder = new TextEncoder ( 'utf-8' ) ; const bytesArray = encoder . encode ( source ) ; const bytesLen = bytesArray . length ; const positions = [ ] ; let row = 0 , column = 0 , pos = { row , column } ; for ( let bytePos = 0 ; bytePos < bytesLen ; bytePos ++ ) { const byte = bytesArray [ bytePos ] ; if ( ( byte & 0b11000000 ) === 0b10000000 ) { positions . push ( null ) ; } else { positions . push ( pos ) ; if ( byte === 10 ) { row += 1 ; column = 0 ; } else { column += 1 ; } pos = { row , column } ; } } positions . push ( pos ) ; function traverse ( node ) { const newNode = node . slice ( ) ; const attrs = node [ 1 ] ; const { begin , end } = attrs ; const range = begin && end && { start : positions [ begin ] , end : positions [ end ] } ; newNode [ 1 ] = { ... attrs , range } ; newNode [ 2 ] = node [ 2 ] . map ( traverse ) ; return newNode ; } return traverse ( syntaxTree ) ; } ; function compileStartedReducer ( state , action ) { const { source } = action ; state . compile . status = CompileStatus . Running ; state . compile . source = source ; } function compileSucceededReducer ( state , action ) { if ( action . platform === 'python' ) { state . compile . status = CompileStatus . Done ; state . stepper . error = null ; } else { const { ast , diagnostics } = action . response ; const source = state . compile . source ; state . compile . status = CompileStatus . Done ; state . compile . syntaxTree = addNodeRanges ( source , ast ) ; state . stepper . error = diagnostics ; } } export function compileFailedReducer ( state ) { state . compile . status = CompileStatus . Error ; clearStepper ( state . stepper ) ; }