import { workspace , commands , InputBoxOptions , window } from 'vscode' ; import { getSymbolKind , SystemVerilogSymbol } from '../symbol' ; /**
 * Processing states:
 * --------------------------------------------
 * INITIAL: before the ports/parameters header.
 * PARAMETERS: inside the parameters header.
 * PORTS: inside the ports header.
 * COMPLETE: processing is finished.
 * --------------------------------------------
 */ enum processingState { INITIAL = 1 , PARAMETERS = 2 , PORTS = 3 , COMPLETE = 4 } /**
 * Key symbols
 */ const ports_key_symbols = [ 'input' , 'output' , 'inout' ] ; const parameter_key_symbol = 'parameter' ; /**
 * Space padding
 */ const padding = ' ' . repeat ( workspace . getConfiguration ( null , null ) . get ( 'editor.tabSize' ) ) ; /**
 * Non-breaking white space
 */ const non_breaking_space = '\xa0' ; param symbol return function isPortSymbol ( symbol ) { if ( isEmptyKey ( symbol ) ) { return false ; } let exists = false ; symbol = symbol . trim ( ) ; ports_key_symbols . forEach ( ( key ) => { if ( symbol === key ) { exists = true ; return false ; } } ) ; return exists ; } param symbol return function isParameterSymbol ( symbol ) { if ( isEmptyKey ( symbol ) ) { return false ; } symbol = symbol . trim ( ) ; return symbol === parameter_key_symbol ; } param key return function isEmptyKey ( key ) { if ( key === undefined || key == null || key === '' ) { return true ; } const regex = new RegExp ( non_breaking_space , 'g' ) ; key = key . replace ( regex , '' ) ; key = key . replace ( / +|\r\n|\n|\r/g , '' ) ; return key . length === 0 ; } param symbol param container return function moduleIsParameterized ( symbol , container ) { if ( isEmptyKey ( symbol ) || isEmptyKey ( container ) ) { return false ; } container = container . replace ( /\r\n|\n|\r/g , ' ' ) ; container = container . replace ( /#\(/g , ' #( ' ) ; container = container . replace ( /  +/g , ' ' ) ; const keys = container . split ( ' ' ) ; if ( keys . length < 3 ) { return false ; } if ( keys [ 0 ] === 'module' && keys [ 1 ] === symbol && keys [ 2 ] === '#(' ) { return true ; } return false ; } /**
 * Module instantiator class which queries a given module, fetches the relative container, and parses an instance.
 */ export class SystemVerilogModuleInstantiator { param query return public auto_instantiate ( query ) { return new Promise ( ( resolve , reject ) => commands . executeCommand ( 'vscode.executeWorkspaceSymbolProvider' , query , undefined , true ) . then ( ( symbols ) => { for ( let i = 0 ; i < symbols . length ; i ++ ) { const s = symbols [ i ] ; if ( s . kind === getSymbolKind ( 'module' ) ) { return s ; } } reject ( new Error ( `${ query } module was not found in the workspace.` ) ) ; } ) . then ( ( s ) => { workspace . openTextDocument ( s . location . uri ) . then ( ( doc ) => { const container = doc . getText ( s . location . range ) ; if ( isEmptyKey ( container ) ) { reject ( new Error ( `${ query }'s definition is undefined in the workspace.` ) ) ; } let instance ; try { instance = formatInstance ( query , container ) ; } catch ( error ) { console . log ( error ) ; reject ( new Error ( `An error occurred when formatting the instance for ${ query }: ${ error }` ) ) ; } if ( instance === undefined ) { reject ( new Error ( `An error occurred when formatting the instance for ${ query }.` ) ) ; } resolve ( instance ) ; } ) ; } ) ) ; } return public instantiateModule ( ) { const options = { prompt : 'Enter the module name to instantiate' , placeHolder : 'Enter the module name to instantiate' } ; window . showInputBox ( options ) . then ( ( value ) => { if ( ! value ) { return ; } const editor = window . activeTextEditor ; if ( editor . selection . isEmpty ) { if ( editor ) { this . auto_instantiate ( value ) . then ( ( v ) => { editor . edit ( ( editBuilder ) => { editBuilder . replace ( editor . selection , v ) ; } ) ; } , ( e ) => { window . showErrorMessage ( e ) ; } ) ; } } } ) ; } } param symbol container return throws export function formatInstance ( symbol , container ) { if ( isEmptyKey ( symbol ) || isEmptyKey ( container ) ) { return undefined ; } const original_container = container ; container = cleanUpContainer ( container ) ; const isParameterized = moduleIsParameterized ( symbol , original_container ) ; const maxLength = findMaxLength ( container , isParameterized ) ; container = parseContainer ( symbol , container , isParameterized , maxLength ) ; return container ; } param container return function cleanUpContainer ( container ) { if ( isEmptyKey ( container ) ) { return undefined ; } container = container . replace ( / /g , ` ${ non_breaking_space } ` ) ; container = container . replace ( /,/g , ' , ' ) ; container = container . replace ( /=/g , ' = ' ) ; container = container . replace ( /\(/g , ' ( ' ) ; container = container . replace ( /\)/g , ' ) ' ) ; container = container . replace ( /\/\//g , ' // ' ) ; container = container . replace ( /\/\*/g , ' /* ' ) ; let regex ; ports_key_symbols . forEach ( ( key ) => { regex = new RegExp ( key , 'g' ) ; container = container . replace ( regex , ` ${ key } ` ) ; } ) ; regex = new RegExp ( parameter_key_symbol , 'g' ) ; container = container . replace ( regex , ` ${ parameter_key_symbol } ` ) ; container = container . replace ( /  +/g , ' ' ) ; container = container . trim ( ) ; return container ; } param container param moduleIsParameterized return function findMaxLength ( container , moduleIsParameterized ) { if ( isEmptyKey ( container ) ) { return undefined ; } const keys = container . split ( ' ' ) ; const output = [ ] ; let maxLength = 0 ; let lastPort ; let lastParameter ; let passedEqualSign = false ; let state = processingState . INITIAL ; for ( let i = 0 ; i < keys . length ; i ++ ) { if ( keys [ i ] === undefined ) { continue ; } if ( keys [ i ] === '//' ) { i = getSingleComment ( keys , output , i ) ; } else if ( keys [ i ] === '/*' ) { i = getBlockComment ( keys , output , i ) ; } else if ( state === processingState . INITIAL ) { if ( keys [ i ] === '(' ) { if ( moduleIsParameterized ) { state = processingState . PARAMETERS ; } else { state = processingState . PORTS ; } } } else if ( state === processingState . PARAMETERS ) { if ( keys [ i ] === ')' ) { state = processingState . PORTS ; } else if ( keys [ i ] === ',' && lastParameter ) { maxLength = Math . max ( lastParameter . length , maxLength ) ; lastParameter = undefined ; passedEqualSign = false ; } else if ( keys [ i ] === '=' ) { passedEqualSign = true ; } else if ( ! isParameterSymbol ( keys [ i ] ) && ! isEmptyKey ( keys [ i ] ) ) { if ( ! passedEqualSign ) { lastParameter = keys [ i ] . trim ( ) ; } } } else if ( state === processingState . PORTS ) { if ( lastParameter ) { maxLength = Math . max ( lastParameter . length , maxLength ) ; lastParameter = undefined ; } if ( keys [ i ] === ')' ) { state = processingState . COMPLETE ; } else if ( keys [ i ] === ',' && lastPort ) { maxLength = Math . max ( lastPort . length , maxLength ) ; lastPort = undefined ; } else if ( ! isPortSymbol ( keys [ i ] ) && ! isEmptyKey ( keys [ i ] ) ) { lastPort = keys [ i ] . trim ( ) ; } } if ( i >= keys . length - 1 ) { if ( state === processingState . PARAMETERS && lastParameter ) { maxLength = Math . max ( lastParameter . length , maxLength ) ; } else if ( state === processingState . PORTS && lastPort ) { maxLength = Math . max ( lastPort . length , maxLength ) ; } } if ( state === processingState . COMPLETE ) { if ( lastPort ) { maxLength = Math . max ( lastPort . length , maxLength ) ; } break ; } } return maxLength ; } param symbol param container param moduleIsParameterized param maxLength return function parseContainer ( symbol , container , moduleIsParameterized , maxLength ) { if ( isEmptyKey ( symbol ) || isEmptyKey ( container ) ) { return undefined ; } if ( maxLength < 0 ) { return undefined ; } const output = [ ] ; const keys = container . split ( ' ' ) ; let lastPort ; let lastParameter ; let lastParameterDefault ; let passedEqualSign = false ; let state = processingState . INITIAL ; for ( let i = 0 ; i < keys . length ; i ++ ) { if ( keys [ i ] === undefined ) { continue ; } if ( keys [ i ] === '//' ) { i = getSingleComment ( keys , output , i ) ; } else if ( keys [ i ] === '/*' ) { i = getBlockComment ( keys , output , i ) ; } else if ( state === processingState . INITIAL ) { if ( keys [ i ] === '(' ) { if ( moduleIsParameterized ) { state = processingState . PARAMETERS ; } else { state = processingState . PORTS ; } } } else if ( state === processingState . PARAMETERS ) { if ( keys [ i ] === ')' ) { state = processingState . PORTS ; } else if ( keys [ i ] === ',' && lastParameter ) { if ( passedEqualSign ) { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameterDefault })` ) ; passedEqualSign = false ; } else { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameter })` ) ; } output . push ( ',\n' ) ; lastParameter = undefined ; } else if ( keys [ i ] === '=' ) { passedEqualSign = true ; } else if ( ! isParameterSymbol ( keys [ i ] ) && ! isEmptyKey ( keys [ i ] ) ) { if ( passedEqualSign ) { lastParameterDefault = keys [ i ] . trim ( ) ; } else { lastParameter = keys [ i ] . trim ( ) ; } } } else if ( state === processingState . PORTS ) { if ( lastParameter ) { if ( passedEqualSign ) { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameterDefault })\n` ) ; passedEqualSign = false ; } else { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameter })\n` ) ; } output . push ( `) u_${ symbol } (\n` ) ; lastParameter = undefined ; } if ( keys [ i ] === ')' ) { state = processingState . COMPLETE ; } else if ( keys [ i ] === ',' && lastPort ) { output . push ( `${ padding }.${ lastPort }${ ' ' . repeat ( maxLength - lastPort . length ) }${ ' ' . repeat ( 4 ) }(${ lastPort })` ) ; output . push ( ',\n' ) ; lastPort = undefined ; } else if ( ! isPortSymbol ( keys [ i ] ) && ! isEmptyKey ( keys [ i ] ) ) { lastPort = keys [ i ] . trim ( ) ; } } if ( i >= keys . length - 1 ) { if ( state === processingState . PARAMETERS && lastParameter ) { if ( passedEqualSign ) { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameterDefault })\n` ) ; passedEqualSign = false ; } else { output . push ( `${ padding }.${ lastParameter }${ ' ' . repeat ( maxLength - lastParameter . length ) }${ ' ' . repeat ( 4 ) }(` ) ; output . push ( `${ lastParameter })\n` ) ; } } else if ( state === processingState . PORTS && lastPort ) { output . push ( `${ padding }.${ lastPort }${ ' ' . repeat ( maxLength - lastPort . length ) }${ ' ' . repeat ( 4 ) }(${ lastPort })` ) ; output . push ( '\n' ) ; } } if ( state === processingState . COMPLETE ) { if ( lastPort ) { output . push ( `${ padding }.${ lastPort }${ ' ' . repeat ( maxLength - lastPort . length ) }${ ' ' . repeat ( 4 ) }(${ lastPort })` ) ; output . push ( '\n' ) ; } break ; } } const instance = [ ] ; if ( moduleIsParameterized ) { instance . push ( `${ symbol } #(\n` ) ; instance . push ( `${ output . join ( '' ) });` ) ; } else { instance . push ( `${ symbol } u_${ symbol }` ) ; instance . push ( ` (\n${ output . join ( '' ) });` ) ; } return instance . join ( '' ) ; } param keys param output param i return function getSingleComment ( keys , output , i ) { if ( ! keys || ! output ) { return undefined ; } if ( i < 0 || i > keys . length ) { return undefined ; } const regex = new RegExp ( non_breaking_space , 'g' ) ; output . push ( padding + keys [ i ] . replace ( regex , ' ' ) ) ; if ( ! keys [ i ] . includes ( '\n' ) && ! keys [ i ] . includes ( '\r' ) ) { i += 1 ; while ( i < keys . length && ! keys [ i ] . includes ( '\n' ) && ! keys [ i ] . includes ( '\r' ) ) { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; i += 1 ; } if ( i < keys . length ) { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; } else { output . push ( '\n' ) ; } } return i ; } param keys param output param i return function getBlockComment ( keys , output , i ) { if ( ! keys || ! output ) { return undefined ; } if ( i < 0 || i > keys . length ) { return undefined ; } const regex = new RegExp ( non_breaking_space , 'g' ) ; output . push ( padding + keys [ i ] . replace ( regex , ' ' ) ) ; i += 1 ; while ( i < keys . length && ! keys [ i ] . trim ( ) . includes ( '*/' ) ) { if ( /\r|\n/ . exec ( keys [ i ] ) ) { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; i += 1 ; if ( i < keys . length ) { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; } } else { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; } i += 1 ; } if ( i < keys . length ) { output . push ( keys [ i ] . replace ( regex , ' ' ) ) ; } return i ; }