import { Utility } from '../misc/utility' ; import { MemoryModel } from '../remotes/Paging/memorymodel' ; import { Remote } from '../remotes/remotebase' ; import { Z80Registers } from '../remotes/z80registers' ; import { SjasmplusSldLabelParser } from './sjasmplussldlabelparser' ; import { Z80asmLabelParser } from './z80asmlabelparser' ; import { Z88dkLabelParser } from './z88dklabelparser' ; import * as fs from 'fs' ; /**
 * For the association of the addresses to the files.
 * modulePrefix and lastLabel are also put here. They are used mainly for hovering.
 * This is not optimal but too much effort to change.
 */ export interface SourceFileEntry { fileName : string ; lineNr : number ; modulePrefix ? : string ; lastLabel ? : string ; } /**
 * The representation of the list file.
 */ export interface ListFileLine extends SourceFileEntry { addr ? : number ; size : number ; line : string ; } /**
 * Entries for the distanceForLabelAddress map.
 * Required would be only the number.
 * But the name of the next label is also stored for debugging
 * purposes.
 */ export interface NextLabelDistance { distance : number ; nextLabel : string ; } /**
 * Calculation of the labels from the input list and labels file.
 *
 * For "normal" list files the labels are 64k addresses.
 * Special assemblers (e.g. sjasmplus) is also able to generate label
 * information that includes the used bank number as well.
 *
 * DeZog is capable of handling both. If banking information should be used as
 * well then the bankSize field is set to something different than 0.
 * This has to be set by the list file parser.
 * Furthermore the list file parser has to provide these 'long addresses'
 * in a special format.
 * Please look at SjasmplusSldLabelParser as an example.
 *
 */ export class LabelsClass { protected filePaths = new Array < string > ( ) ; protected fileLineNrs = new Map < number , SourceFileEntry > ( ) ; protected lineArrays = new Map < string , Array < number > > ( ) ; protected labelsForNumber64k = new Array < any > ( 0x10000 ) ; protected labelsForLongAddress = new Map < number , Array < string > > ( ) ; protected distanceForLabelAddress = new Map < number , NextLabelDistance > ( ) ; protected numberForLabel = new Map < string , number > ( ) ; protected labelsHierarchy = new Map < string , any > ( ) ; protected labelLocations = new Map < string , { file : string ; lineNr : number ; address : number ; } > ( ) ; protected watchPointLines = new Array < { address : number ; line : string ; } > ( ) ; protected assertionLines = new Array < { address : number ; line : string ; } > ( ) ; protected logPointLines = new Array < { address : number ; line : string ; } > ( ) ; protected smallValuesMaximum : number ; protected bankSize : number ; protected warnings : string ; /**
     * Initializes the lists/arrays.
     */ public init ( smallValuesMaximum ) { this . fileLineNrs . clear ( ) ; this . lineArrays . clear ( ) ; this . labelsForNumber64k . fill ( undefined ) ; this . labelsForLongAddress . clear ( ) ; this . numberForLabel . clear ( ) ; this . labelLocations . clear ( ) ; this . labelsHierarchy . clear ( ) ; this . watchPointLines . length = 0 ; this . assertionLines . length = 0 ; this . logPointLines . length = 0 ; this . filePaths . length = 0 ; this . smallValuesMaximum = smallValuesMaximum ; this . bankSize = 0 ; this . warnings = undefined as any ; } /**
     * Returns true if long addresses have been used.
     * I.e. if bankSize != 0.
     */ public AreLongAddressesUsed ( ) { return this . bankSize != 0 ; } /**
     * This has to be set in the launchRequest.
     * Finishes off the loading of list and labels files.
     * Can throw an exception if some values make no sense.
     */ public finish ( ) { this . calculateLabelOffsets ( ) ; this . calculateLabelDistances ( ) ; this . createLabelHierarchy ( ) ; } param mainConfig public readListFiles ( mainConfig ) { this . warnings = '' ; if ( mainConfig . sjasmplus ) { for ( const config of mainConfig . sjasmplus ) { const parser = new SjasmplusSldLabelParser ( this . fileLineNrs , this . lineArrays , this . labelsForNumber64k , this . labelsForLongAddress , this . numberForLabel , this . labelLocations , this . watchPointLines , this . assertionLines , this . logPointLines ) ; parser . loadAsmListFile ( config ) ; this . bankSize = parser . bankSize ; const warnings = parser . getWarnings ( ) ; if ( warnings ) this . warnings += 'sjasmplus sld parser warnings (' + config . path + '):\n' + warnings ; this . filePaths . push ( config . path ) ; } } if ( mainConfig . z80asm ) { const parser = new Z80asmLabelParser ( this . fileLineNrs , this . lineArrays , this . labelsForNumber64k , this . labelsForLongAddress , this . numberForLabel , this . labelLocations , this . watchPointLines , this . assertionLines , this . logPointLines ) ; for ( const config of mainConfig . z80asm ) { parser . loadAsmListFile ( config ) ; this . filePaths . push ( config . path ) ; } } if ( mainConfig . z88dk ) { const parser = new Z88dkLabelParser ( this . fileLineNrs , this . lineArrays , this . labelsForNumber64k , this . labelsForLongAddress , this . numberForLabel , this . labelLocations , this . watchPointLines , this . assertionLines , this . logPointLines ) ; for ( const config of mainConfig . z88dk ) { parser . loadAsmListFile ( config ) ; this . filePaths . push ( config . path ) ; } } if ( this . warnings == '' ) this . warnings = undefined as any ; this . finish ( ) ; } param date param path returns protected youngestDate ( date , path ) { const stats = fs . statSync ( path ) ; const fileDate = new Date ( stats . mtimeMs ) ; const youngestDate = ( date < fileDate ) ? fileDate : date ; return youngestDate ; } /**
     * Returns the warnings.
     * undefined if no warnings.
     */ public getWarnings ( ) { return this . warnings ; } /**
     * Accessor for the watchpoint lines.
     * Long addresses.
     */ public getWatchPointLines ( ) { return this . watchPointLines ; } /**
     * Accessor for the assertion lines.
     * Long addresses.
     */ public getAssertionLines ( ) { return this . assertionLines ; } /**
     * Accessor for the logpoint lines.
     * Long addresses.
     */ public getLogPointLines ( ) { return this . logPointLines ; } /**
     * Calculates the offsets for all labels.
     * I.e. for all addresses without a direct label entry.
     * Deals with 64k addresses only.
     * E.g. labelsForNumber:
     * [0x0000] undefined
     * [0x0001] undefined
     * ...
     * [0x8000]	stack_bottom
     * [0x8001] 1
     * [0x8002] 2
     * [0x8003] 3
     * [0x8004] 4
     * [0x8005] stack_top
     * [0x8006] 1
     * ...
     */ protected calculateLabelOffsets ( ) { let offs = - 1 ; for ( let i = 0 ; i < 0x10000 ; i ++ ) { const labels = this . labelsForNumber64k [ i ] ; if ( labels === undefined ) { if ( offs >= 0 ) { this . labelsForNumber64k [ i ] = offs ; ++ offs ; } } else { offs = 1 ; } } } /**
     * Calculates the distance array (distance to next label, or estimated size of a label).
     * Works also with long addresses and fills the 'distanceForLabelAddress' map.
     * It has entries for each long address with it's size.
     * The last label address will stay undefined as no distance to the next label can be measured.
     * Similar, if the next label is smaller (maybe because a different bank is used or
     * a smaller EQU has been defined).
     * This is used for estimating the data that should be displayed on hovering or if no
     * data size is specified in the WATCH window.
     * It covers only main cases and the output might not be accurate. e.g. the following
     * won't work:
     * ~~~
     *  ORG 0x8000
     *  label: defb 1
     *  val: equ 0x7000
     * ~~~
     * Because val is < label, address 0x8000 gets no size entry.
     */ protected calculateLabelDistances ( ) { let prevAddr ; for ( let [ labelName , longAddr ] of this . numberForLabel ) { if ( prevAddr != undefined ) { const dist = ( longAddr & 0xFFFF ) - ( prevAddr & 0xFFFF ) ; if ( dist > 0 ) { this . distanceForLabelAddress . set ( prevAddr , { distance : dist , nextLabel : labelName } ) ; } } prevAddr = longAddr ; } } param longAddress returns public getLabelsForLongAddress ( longAddress ) { const labels = this . labelsForLongAddress . get ( longAddress ) ; return labels || [ ] ; } param number param regsAsWell returns public getLabelsForNumber64k ( number , regsAsWell = false ) { number &= 0xFFFF ; if ( number <= this . smallValuesMaximum ) { return [ ] ; } let names ; if ( regsAsWell ) names = Remote . getRegistersEqualTo ( number ) ; else names = new Array < string > ( ) ; const labels = this . labelsForNumber64k [ number ] ; if ( labels && typeof labels !== 'number' ) { names . push ( ... labels ) ; } return names ; } param addr returns public getDistanceToNextLabel ( addr ) { const nextLabel = this . distanceForLabelAddress . get ( addr ) ; return nextLabel ?. distance ; } param number param regsAsWell returns public getLabelsPlusIndexForNumber64k ( number , regsAsWell = false ) { if ( number <= this . smallValuesMaximum || number > 0xFFFF ) { return [ ] ; } let names ; if ( regsAsWell ) names = Remote . getRegistersEqualTo ( number ) ; else names = new Array < string > ( ) ; let labels = this . labelsForNumber64k [ number ] ; if ( labels ) { if ( typeof labels !== 'number' ) { names . push ( ... labels ) ; } else { const offs = labels ; number -= offs ; const baseLabels = this . labelsForNumber64k [ number ] ; if ( baseLabels !== undefined ) { const labelsPlus = baseLabels . map ( label => label + '+' + offs ) ; names . push ( ... labelsPlus ) ; } } } return names ; } param label returns public getNumberForLabel ( label ) { return this . numberForLabel . get ( label ) ; } param label returns { file public getLocationOfLabel ( label ) { return this . labelLocations . get ( label ) ; } param labelRegEx param options returns public getLabelsForRegEx ( labelRegEx , options = 'i' ) { const regex = new RegExp ( labelRegEx , options ) ; const foundLabels = new Array < string > ( ) ; for ( let [ k , ] of this . numberForLabel ) { const match = regex . exec ( k ) ; if ( match ) foundLabels . push ( k ) ; } return foundLabels ; } /**
     * Create the hierarchy of labels.
     */ protected createLabelHierarchy ( ) { for ( let [ label , ] of this . numberForLabel ) { const parts = label . split ( '.' ) ; if ( parts [ 0 ] == '' ) { parts . shift ( ) ; parts [ 0 ] = '.' + parts [ 0 ] ; } let map = this . labelsHierarchy ; for ( const part of parts ) { let subMap = map . get ( part ) ; if ( ! subMap ) { subMap = new Map < string , any > ( ) ; map . set ( part , subMap ) ; } map = subMap ; } } } param label returns public getSubLabels ( label ) { const parts = label . split ( '.' ) ; let map = this . labelsHierarchy ; for ( const part of parts ) { let subMap = map . get ( part ) ; if ( ! subMap ) { subMap = new Map < string , any > ( ) ; map . set ( part , subMap ) ; } map = subMap ; } return Array . from ( map . keys ( ) ) ; } param text returns public getNumberFromString64k ( text ) { if ( text == undefined ) return NaN ; let result = this . getNumberForLabel ( text ) ; if ( result == undefined ) { if ( text . startsWith ( '_' ) ) return NaN ; result = Utility . parseValue ( text ) ; } if ( isNaN ( result ) ) return result ; return result & 0xFFFF ; } param address returns public getFileAndLineForAddress ( address ) { let entry = this . fileLineNrs . get ( address ) ; if ( ! entry ) { entry = this . fileLineNrs . get ( address & 0xFFFF ) ; if ( ! entry ) { return { fileName : '' , lineNr : 0 , modulePrefix : undefined , lastLabel : undefined } ; } } const filePath = Utility . getAbsFilePath ( entry . fileName ) ; return { fileName : filePath , lineNr : entry . lineNr , modulePrefix : entry . modulePrefix , lastLabel : entry . lastLabel } ; } param fileName param lineNr returns public getModuleAndLastLabelForFileAndLine ( fileName , lineNr ) { const filePath = Utility . getRelFilePath ( fileName ) ; const result = { modulePrefix : '' , lastLabel : '' } ; let longAddr ; const lineArray = this . lineArrays . get ( filePath ) ; if ( ! lineArray ) return result ; while ( true ) { if ( lineNr < 0 ) return { modulePrefix : '' , lastLabel : '' } ; longAddr = lineArray [ lineNr ] ; if ( longAddr != undefined ) break ; lineNr -- ; } const entry = Labels . getFileAndLineForAddress ( longAddr ) ; result . modulePrefix = entry . modulePrefix ! ; result . lastLabel = entry . lastLabel ! ; return result ; } param fileName param lineNr returns public getAddrForFileAndLine ( fileName , lineNr ) { const filePath = Utility . getRelFilePath ( fileName ) ; let addr = - 1 ; const lineArray = this . lineArrays . get ( filePath ) ; if ( lineArray ) { addr = lineArray [ lineNr ] ; if ( addr == undefined ) addr = - 1 ; } return addr ; } /**
     * Checks if the target's memory model matches the model used during parsing.
     */ public convertLabelsTo ( memModel ) { if ( this . bankSize == 0 ) return ; const targetBankSize = memModel . getBankSize ( ) ; this . convertLabelsToBankSize ( targetBankSize ) ; } param targetBankSize protected convertLabelsToBankSize ( targetBankSize ) { const newFileLines = new Map < number , SourceFileEntry > ( ) ; for ( let [ addr , sourceEntry ] of this . fileLineNrs ) { addr = this . convertAddressToBankSize ( addr , targetBankSize ) ; newFileLines . set ( addr , sourceEntry ) ; } this . fileLineNrs = newFileLines ; for ( const [ , lineArray ] of this . lineArrays ) { const count = lineArray . length ; for ( let i = 0 ; i < count ; i ++ ) { let addr = lineArray [ i ] ; if ( addr == undefined ) continue ; addr = this . convertAddressToBankSize ( addr , targetBankSize ) ; lineArray [ i ] = addr ; } } const newLabelsForLongAddress = new Map < number , string [ ] > ( ) ; for ( let [ addr , labels ] of this . labelsForLongAddress ) { addr = this . convertAddressToBankSize ( addr , targetBankSize ) ; newLabelsForLongAddress . set ( addr , labels ) ; } this . labelsForLongAddress = newLabelsForLongAddress ; const newNumberForLabel = new Map < string , number > ( ) ; for ( let [ label , addr ] of this . numberForLabel ) { addr = this . convertAddressToBankSize ( addr , targetBankSize ) ; newNumberForLabel . set ( label , addr ) ; } this . numberForLabel = newNumberForLabel ; const newLabelLocations = new Map < string , { file : string ; lineNr : number ; address : number ; } > ( ) ; for ( let [ label , location ] of this . labelLocations ) { location . address = this . convertAddressToBankSize ( location . address , targetBankSize ) ; newLabelLocations . set ( label , location ) ; } this . labelLocations = newLabelLocations ; for ( const line of this . watchPointLines ) { line . address = this . convertAddressToBankSize ( line . address , targetBankSize ) ; } for ( const line of this . assertionLines ) { line . address = this . convertAddressToBankSize ( line . address , targetBankSize ) ; } for ( const line of this . logPointLines ) { line . address = this . convertAddressToBankSize ( line . address , targetBankSize ) ; } } param address param targetBankSize returns protected convertAddressToBankSize ( address , targetBankSize ) { if ( targetBankSize == 0 ) { address &= 0xFFFF ; } else { const origBank = Z80Registers . getBankFromAddress ( address ) ; const newBank = origBank * this . bankSize / targetBankSize ; address = Z80Registers . getLongAddressWithBank ( address & 0xFFFF , newBank ) ; } return address ; } } export const Labels = new LabelsClass ( ) ;