import { readFileSync } from 'fs' ; import { Utility } from '../misc/utility' ; import { AsmConfigBase , SjasmplusConfig } from '../settings' ; import { LabelParserBase } from './labelparserbase' ; import { SourceFileEntry } from './labels' ; /**
 * This class parses sjasmplus sld file.
 * SLD stands for Source Level Debugging.
 * 'SLD data are extra "tracing" data produced during assembling for debuggers and IDEs,
 * similar to "map" files already supported by sjasmplus (LABELSLIST and CSPECTMAP).
 * The debugger can read these data, and with non-tricky source producing machine code
 * with correct device memory mapping, the debugger can trace the origins of every
 * instruction back to the original source code line, and display the source instead/along
 * the disassembly view (the "map" files mentioned above provide only list of labels which
 * is usually already super helpful, but can't track the source origins of each instruction).'
 * See https://z00m128.github.io/sjasmplus/documentation.html#idp10
 *
 * A few notes:
 * - numberForLabel and labelsForNumber will normally only get 64k
 *   addresses/values (unless an EQU is by itself bigger than 0xFFFF.
 * - fileLineNrs and lineArrays will get potentially long address.
 *   I.e. their value is either a normal 64k address or a long address
 *   with bank number.
 *
 */ export class SjasmplusSldLabelParser extends LabelParserBase { public bankSize : number = 0 ; protected regexSkipSldOptComment = /^;\s*sldopt/i ; protected estimatedFileLineNrs = new Map < number , SourceFileEntry > ( ) ; /**
     * Reads the given sld file.
     * As the SLD file is easy to read only one pass is required.
     */ public loadAsmListFile ( config ) { this . config = config ; const sldConfig = this . config as SjasmplusConfig ; if ( sldConfig . excludeFiles . length > 0 ) throw Error ( "You cannot use 'excludeFiles' in a sjasmplus configuration." ) ; this . lastLabel = undefined as any ; const sldLinesFull = readFileSync ( sldConfig . path ) . toString ( ) . split ( '\n' ) ; const sldLines = sldLinesFull . map ( line => line . trimRight ( ) ) ; this . checkSldVersion ( sldLines ) ; this . parseForBankSizeAndSldOpt ( sldLines ) ; if ( ( config as SjasmplusConfig ) . disableBanking ) this . bankSize = 0 ; for ( const line of sldLines ) { this . parseFileLabelAddress ( line ) ; } for ( let [ address , entry ] of this . estimatedFileLineNrs ) { if ( ! this . fileLineNrs . get ( address ) ) { this . fileLineNrs . set ( address , entry ) ; } } } /**
     * Checks the SLD file version and throws an exception if too old.
     */ protected checkSldVersion ( lines ) { if ( lines . length < 1 ) throw Error ( "'" + this . config . path + "' is empty." ) ; const fields = lines [ 0 ] . split ( '|' ) ; if ( fields [ 1 ] != 'SLD.data.version' ) throw Error ( "'" + this . config . path + "': SLD data version not found." ) ; const version = fields [ 2 ] || '0' ; const requiredVersion = 1 ; if ( parseInt ( version ) < requiredVersion ) throw Error ( "'" + this . config . path + "': SLD data version " + version + " is too old. Need SLD version " + requiredVersion + ". Please update sjasmplus to at least version 1.18.0." ) ; } /**
     * Parses the complete file to get the bank size.
     */ protected parseForBankSizeAndSldOpt ( lines ) { let keywords = [ ] ; let bankSize ; for ( const line of lines ) { const fields = line . split ( '|' ) ; const type = fields [ 6 ] ; if ( type == 'Z' ) { const data = fields [ 7 ] ; const numberString = data . replace ( /[^0-9,]/g , '' ) ; bankSize = parseInt ( numberString ) ; } if ( line . startsWith ( '||K|KEYWORDS|' ) ) { keywords = fields [ 4 ] . split ( ',' ) ; } if ( bankSize != undefined && keywords != undefined ) break ; } if ( bankSize == undefined ) { throw Error ( "Could not find bank size in SLD file. Did you forget to set the 'DEVICE' in your assembler file? If you use a non ZX Spectrum device you need to choose NOSLOT64K." ) ; } this . bankSize = bankSize ; const kws = [ "WPMEM" , "LOGPOINT" , "ASSERTION" ] ; let missing = [ ] ; for ( const kw of kws ) { if ( keywords . indexOf ( kw ) < 0 ) missing . push ( kw ) ; } if ( missing . length > 0 ) { const missingStr = missing . join ( ', ' ) ; this . warnings += "The assembler file is missing the 'SLDOPT COMMENT " + missingStr + "' statement. Use of " + missingStr + " is not possible." ; } } param line protected parseFileLabelAddress ( line ) { const fields = line . split ( '|' ) ; let sourceFile = fields [ 0 ] ; if ( sourceFile == '' ) return ; sourceFile = Utility . getRelSourceFilePath ( sourceFile , this . config . srcDirs ) ; const page = parseInt ( fields [ 4 ] ) ; let value = parseInt ( fields [ 5 ] ) ; const type = fields [ 6 ] ; const label = fields [ 7 ] ; switch ( type ) { case 'L' : { const lbls = label . split ( ',' ) ; const trait = lbls [ 3 ] ; this . modulePrefix = lbls [ 0 ] ; if ( trait == "+endmod" ) { const modArr = this . modulePrefix . split ( '.' ) ; modArr . pop ( ) ; this . modulePrefix = modArr . join ( '.' ) ; } if ( this . modulePrefix ) this . modulePrefix += '.' ; const mainLabel = lbls [ 1 ] ; if ( ! mainLabel ) break ; this . lastLabel = mainLabel ; const localLabel = lbls [ 2 ] ; let fullLabel = mainLabel ; if ( this . modulePrefix ) fullLabel = this . modulePrefix + mainLabel ; if ( localLabel ) fullLabel += '.' + localLabel ; if ( fullLabel ) { const longValue = this . createLongAddress ( value , page ) ; this . addLabelForNumberRaw ( longValue , fullLabel ) ; const lineNr = parseInt ( fields [ 1 ] ) - 1 ; this . labelLocations . set ( fullLabel , { file : sourceFile , lineNr , address : longValue } ) ; } } break ; case 'T' : { const address = this . createLongAddress ( value , page ) ; const lineNr = parseInt ( fields [ 1 ] ) - 1 ; this . fileLineNrs . set ( address , { fileName : sourceFile , lineNr : lineNr , modulePrefix : this . modulePrefix , lastLabel : this . lastLabel } ) ; const endAddress = this . addressAdd4 ( address ) ; for ( let addrInside = address + 1 ; addrInside < endAddress ; addrInside ++ ) { this . estimatedFileLineNrs . set ( addrInside , { fileName : sourceFile , lineNr : lineNr , modulePrefix : this . modulePrefix , lastLabel : this . lastLabel } ) ; } let lineArray = this . lineArrays . get ( sourceFile ) ; if ( ! lineArray ) { lineArray = new Array < number > ( ) ; this . lineArrays . set ( sourceFile , lineArray ) ; } if ( lineArray [ lineNr ] == undefined ) { lineArray [ lineNr ] = address ; } } break ; case 'K' : { const comment = fields [ 7 ] ; const address = this . createLongAddress ( value , page ) ; this . findWpmemAssertionLogpoint ( address , comment ) ; } break ; } } /**
     * Calls super, but only if the line does not start with ";SLDOPT".
     * I.e. it filters any commented SLDOPT line.
     */ protected findWpmemAssertionLogpoint ( address , line ) { const match = this . regexSkipSldOptComment . exec ( line ) ; if ( match ) return ; super . findWpmemAssertionLogpoint ( address , line ) ; } param address returns protected addressAdd4 ( address ) { if ( address & ~ 0xFFFF ) { const mask = this . bankSize - 1 ; let addrBank = address & mask ; addrBank += 4 ; if ( addrBank > mask ) addrBank = mask ; addrBank += address & ~ mask ; return addrBank ; } else { let addr2 = address + 4 ; if ( addr2 > 0xFFFF ) addr2 = 0xFFFF ; return addr2 ; } } param address param bank returns protected createLongAddress ( address , bank ) { let result = address ; if ( this . bankSize != 0 ) result += ( bank + 1 ) << 16 ; return result ; } }