import { EventEmitter } from 'events' ; import { LogCustomCode } from '../../log' ; import { Utility } from '../../misc/utility' ; import { readFileSync } from 'fs' ; /**
 * A class that communicates directly with the custom javascript code.
 * The problem here is that an infinite loop in the custom code can make DeZog hang.
 * It is not possible to move this to worker threads as this would mean every
 * Z80 instruction would have to be executed asynchronously (performance penalty).
 * The problem can be solved if the Z80 simulator is completely moved to a webview.
 */ export class CustomCodeAPI extends EventEmitter { public tstates : number = 0 ; public unitTestLabel : string | undefined ; protected parent : CustomCode ; param parent constructor ( parent , unitTestLabel ? ) { super ( ) ; this . parent = parent ; this . unitTestLabel = unitTestLabel ; } /**
     * Just used to place a breakpoint.
     */ public debugBreak ( ) { } param message public sendToCustomUi ( message ) { LogCustomCode . log ( 'sendToCustomUi: ' + JSON . stringify ( message ) ) ; this . parent . emit ( 'sendToCustomUi' , message ) ; } param message public receivedFromCustomUi : ( message : any ) => void = undefined as any ; /**
     * Called when time has advanced.
     * Can be overwritten by the user.
     * Note: API.tstates contains the number of t-states passed
     * since start of simulation/debug session.
     */ public tick : ( ) => void = undefined as any ; param port return public readPort : ( port : number ) => number | undefined = ( port ) => undefined ; param port param value public writePort : ( port : number , value : number ) => void = ( port , value ) => { } ; param non_maskable param data public generateInterrupt ( non_maskable , data ) { this . parent . emit ( 'interrupt' , non_maskable , data ) ; } /**
     * This is called once at the start as soon as the UI is ready to
     * sent and receive message.
     * You can override this to e.g. sent first initialized values to the UI.
     * You can also leave this empty and set the values initially from the UI code.
     * Note: The custom logic is instantiated before the UI.
     */ public uiReady : ( ) => void = ( ) => { } ; param . . args public log ( ... args ) { LogCustomCode . log ( ... args ) ; } } /**
 * A class to execute custom code in the simulator.
 * It is called by the (Z80) ports and will execute the javascript code.
 * And it also received messages from the ZSimulationview and
 * as well can send messages to the ZSimulationView.
 */ export class CustomCode extends EventEmitter { public static addDiagnosticsErrorFunc : ( ( message : string , filepath : string , line : number , column : number ) => void ) | undefined ; /**
     * Static method that calls 'eval' with a context.
     * Not used anymore, see runInContext.
     */ protected static evalInContext ( js , context ) { return function ( ) { try { return eval ( js ) ; } catch ( e ) { e . message = 'Custom Code: ' + e . message ; throw e ; } } . call ( context ) ; } param js param context param filename param lineOffset param timeout protected static runInContext ( js , context , filename , lineOffset , timeout = 2000 ) { try { Utility . runInContext ( js , context , filename , lineOffset , timeout ) ; } catch ( e ) { e . message = 'Custom Code: ' + e . message ; if ( this . addDiagnosticsErrorFunc && e . position ) { this . addDiagnosticsErrorFunc ( e . message , e . position . filename , e . position . line , e . position . column ) ; } throw e ; } } protected context : any ; protected jsCode : string ; protected jsPath : string ; protected api : CustomCodeAPI ; param jsPath constructor ( jsPath ) { super ( ) ; this . load ( jsPath ) ; } param jsPath public load ( jsPath ) { this . jsCode = readFileSync ( jsPath ) . toString ( ) ; this . jsPath = jsPath ; } param unitTestLabel param timeout public execute ( unitTestLabel ? , timeout = 2000 ) { this . api = new CustomCodeAPI ( this , unitTestLabel ) ; this . context = { tmpAPI : this . api } ; const preamble = `
// Preamble:
const global = this;
const API = global.tmpAPI;
// 'tmpAPI' is not visible to customer code. Use 'API' instead.
delete global.tmpAPI;

// Entry point for debugging:
API.debugBreak();

// Add a log to show that log starts
API.log('\\n=====================================');
API.log('Custom code: init start');

` ; const allCode = `${ preamble }${ this . jsCode }

API.log('Custom code: init end');
API.log('-------------------------------------\\n');` ; const lineOffset = Utility . countOccurencesOf ( '\n' , preamble ) ; CustomCode . runInContext ( allCode , this . context , this . jsPath , - lineOffset , timeout ) ; } /**
     * This is called once at the start as soon as the UI is ready to
     * sent and receive message.
     * You can override this to e.g. sent first initialized values to the UI.
     * You can also leave this empty and set the values initially from the UI code.
     * Note: The custom logic is instantiated before the UI.
     */ public uiReady ( ) { if ( this . api ) { LogCustomCode . log ( 'API.uiReady() called.' ) ; this . api . uiReady ( ) ; } } param port return public readPort ( port ) { this . logTstates ( ) ; LogCustomCode . log ( 'API.readPort(' + Utility . getHexString ( port , 4 ) + 'h)' ) ; let value ; try { value = this . api . readPort ( port ) ; LogCustomCode . log ( '  Reading value ' + Utility . getHexString ( value , 2 ) + 'h for port ' + Utility . getHexString ( port , 4 ) + 'h' ) ; } catch ( e ) { this . throwError ( "Error during executing custom java script in 'readPort': " + e . message ) ; } return value ; } param port param value public writePort ( port , value ) { this . logTstates ( ) ; LogCustomCode . log ( 'API.writePort(' + Utility . getHexString ( port , 4 ) + 'h , ' + Utility . getHexString ( value , 2 ) + 'h)' ) ; try { this . api . writePort ( port , value ) ; } catch ( e ) { this . throwError ( "Error during executing custom java script in 'writePort': " + e . message ) ; } } param message public receivedFromCustomUi ( message ) { LogCustomCode . log ( 'API.receivedFromCustomUi: ' + JSON . stringify ( message ) ) ; if ( this . api . receivedFromCustomUi == undefined ) { LogCustomCode . log ( "  But no custom 'this.receivedFromCustomUi' defined." ) ; } else { try { this . api . receivedFromCustomUi ( message ) ; } catch ( e ) { this . throwError ( "Error during executing custom java script in 'API.receivedFromCustomUi': " + e . message ) ; } } } param tstates public setTstates ( tstates ) { this . api . tstates = tstates ; } /**
     * Logs the t-states.
     */ public logTstates ( ) { LogCustomCode . log ( 'tick: tstates=' + this . api . tstates ) ; } /**
     * A call to inform the custom code about the advanced time.
     * The user can control through 'timeSteps' in which interval
     * this is called.
     */ public tick ( ) { if ( this . api . tick == undefined ) return ; try { this . logTstates ( ) ; this . api . tick ( ) ; } catch ( e ) { this . throwError ( "Error during executing custom java script in 'tick': " + e . message ) ; } } param errorMessage protected throwError ( errorMessage ) { LogCustomCode . log ( errorMessage ) ; throw Error ( errorMessage ) ; } } ;