import { IColumnOption , IHoldModulopt , IOptions } from "./interfaces"; // eslint-disable-next-line @typescript-eslint/no-unused-vars const defaultCall = ( key: string , previousKey: string , value: any ): number => { return 0; }; /** * Masks can be written following this format 11.1111.11 (with dots to avoid mistakes) * so it is necessary to strip all dots out of the masks so we can process them as real * masks * @param s * @returns */ const stripDots = ( s: string ): string => s.replace( /[.]/g , "" ); const substituteTwos = ( s: string ): string => s.replace( /2/g , "1" ); export class MaskBuilder { private static instance: MaskBuilder; private constructor() {} public static getInstance(): MaskBuilder { if ( !MaskBuilder.instance ) { MaskBuilder.instance = new MaskBuilder(); } return MaskBuilder.instance; } /** * Gives a bit representation for every multiChoices option * @param object * @param row entry [optionName [,default][,multiChoices] ] * @param cpt counter * @param totalOffset totalizer of the offset */ public assignValuePerBit( modulopt: IHoldModulopt , row: any , cpt: number , totalOffset: number ) { if ( row[ 2 ] ) { // compute the current element offset by looking at the length of its 3rd collumn const offset = row[ 2 ].length; // the option name is in the first collumn const option = row[ 0 ]; // initialisation on the object that will host the options modulopt.masks[ option ] = {} as IOptions; // for every option that is coded on multiple bit, we compute the range of bits that // it takes by using the incrementation for ( let i = 0; i < offset; ++i ) { // get the pow2 of the cpt value ==> to transform into binary representation const bit = Math.pow( 2 , cpt + i ); // get the representation with dots every 4th digit const representation = this.formatedNumberRepresentation( bit , totalOffset ); // undefined and function references cannot be added so we just tell what we were supposed to have if ( typeof row[ 2 ][ i ] === "function" || row[ 2 ][ i ] === void 0 ) { row[ 2 ][ i ] = ( typeof row[ 2 ][ i ] ).toUpperCase(); } // assign the matching bit to the option coded on multiple bits modulopt.masks[ option ][ representation ] = row[ 2 ][ i ]; } } } /** * Every option will occupied a certain amount of space as a bit reprensentation so to be sure nothing * overlaps, and mostly, have consistent zero-filled (padded) masks, we have to compute the total offset * @param optionVector * @returns */ public computeOffset( optionVector: any[] ) { let offset = 0; optionVector .filter( ( row ) => ( typeof row[ 1 ] === "boolean" && row.length === 2 ) || row[ 2 ] ) .map( ( row ) => { // sum an offset stored in a cell of an array (may not exist so fallback of 1) offset += row[ 2 ] ? row[ 2 ].length : 1; } ); // ceiled integer division multiplied by 4 so we have multiples of four. Perfect for bin reprsentation return Math.ceil( offset / 4 ) * 4; } /** * Transform a number into its sero filled dotted binary format. 1 => 0000.0000.0001 * * @param optDef defines the default value for an option and its string masks * @param offset the offset teken by possible values of the mask in binary : 1 take , 0 leave * @param totalOffset indicate the whole space taken by all option mask + 0 fill included * @param cpt the general counter */ public assignMasks( optDef: IColumnOption , totalOffset: number , cpt: number ) { const representation = this.formatedNumberRepresentation( Math.pow( 2 , cpt ) , totalOffset ); // (join every slices by a dot then push to the masks field) // [0000,0000,1000] => ["0000.0000.1000","0000.0001.0000"] if ( representation !== "-1" ) { optDef.mask = representation; } } /** * Transforms a number value to its binary representation with 1 dot every 4th bit * @param value * @param totalOffset * @returns */ public formatedNumberRepresentation( value: number , totalOffset: number ) { // transform the position on the iteration into a binary representation as it is a power of 2 bin // 2^(cpt+i) => 1, 2, 4, 8 => 0 , 1 , 10 , 100 , 1000 const binRep = this.dec2bin( value ); // = for every 4 bit represented, add a dot so it is easier to manupulate as a human = // (division into array of strings) => 000000001000 => [0000,0000,1000] const sliceOfFour: RegExpMatchArray | null = this.pad( binRep , totalOffset ).match( /.{1,4}/g ); if ( sliceOfFour ) { return sliceOfFour.join( "." ); } return "-1"; } public defineInterval( row: any , cpt: number ): number[] { return typeof row[ 1 ] === "boolean" && row.length === 2 ? [ cpt ] : row[ 2 ] ? [ cpt , -1 + cpt + row[ 2 ].length ] : [ cpt ]; } public getOptionsFromMask( modulopt: IHoldModulopt , optionMask: string ): any { const options: any = {}; const masks = this.masksMappedByName( modulopt.masks ); Object.keys( masks ).map( ( k ) => { options[ k ] = this.chosenFromMask( modulopt , optionMask , k ); } ); return options; } /** * Transforms decimal number into binary representation * @param dec * @returns */ private dec2bin( dec: number ) { return ( dec >>> 0 ).toString( 2 ); } /** * Pads zero (zero fill a number). It provides a string since 0 before any number is not significant * @param num the number that has to gain the padding * @param size the offset of the resulting string * @returns */ private pad( num: string , size: number ): string { num = num.toString(); while ( num.length < size ) num = "0" + num; return num; } private masksMappedByName( masks: IOptions , cb = defaultCall , previousKey = "" ) { const mapped: IOptions = {}; for ( const [ key , value ] of Object.entries( masks ) ) { if ( typeof value === "string" || previousKey ) { mapped[ value ] = key; const result: number = cb( key , previousKey , value ); if ( result != 0 ) { return result; } } else if ( typeof value === "object" ) { mapped[ key ] = this.masksMappedByName( value , cb , key ); } } return mapped; } private applyMasks( masks: IOptions , a: string , maskField: string ) { let result = 0; const onAssociation = ( key: string , previousKey: string , value: any ): number => { if ( maskField === value || maskField === previousKey ) { // the key is the actual mask const b = stripDots( key as string ); // bitwise comparison on base 2 result = parseInt( a , 2 ) & parseInt( b , 2 ); if ( result != 0 ) { return result; } } return 0; }; // invert keys and values of the masks so we have option name as index of the object this.masksMappedByName( masks , onAssociation ); return result; } /** * from a string mask containing 0 - 1 - 2, that produce an other mask; * "2" => "true":1 ... "1" => "false":0 ... "0" => default : "-" * @param setMask */ private guessMaskFromMask( setMask: string ): string { const result = []; let i = 0; setMask = stripDots( setMask ); for ( ; i < setMask.length; ++i ) { const c = setMask.charAt( i ); result.push( c === "2" ? 1 : c === "1" ? 0 : "-" ); } return result.join( "" ); } private defineSortOption( modulopt: IHoldModulopt , bit: number , maskField: string ) { const offset = modulopt.optionsOffset; const representation = this.formatedNumberRepresentation( bit , offset ); return modulopt.masks[ maskField ][ representation ]; } private defineBooleansOption( defaults: IOptions , bit: string , option: string ) { switch ( bit ) { case "1" : return true; case "0" : return false; default : return defaults[ option ]; } } private chosenFromMask( modulopt: IHoldModulopt , setMask: string , maskField: string ) { const a = substituteTwos( stripDots( setMask ) ); const c = this.guessMaskFromMask( setMask ); const result = this.applyMasks( modulopt.masks , a , maskField ); if ( result > 0 ) { const bitPosFromRight = Math.log2( result ); const position = -1 + c.length - bitPosFromRight; const offset = modulopt.optionsOffset; const representation = this.formatedNumberRepresentation( result , offset ); const booleanMask = modulopt.masks[ representation ]; // does the result indicates a bit value that aims a boolean ? if ( typeof booleanMask === "string" ) { const result = this.defineBooleansOption( modulopt.defaults , c[ position ] , booleanMask ); return result; } else { // INFO: Treat non binar cases return this.defineSortOption( modulopt , result , maskField ); } } const option = /[\d.]/g.test( maskField ) ? modulopt.masks[ maskField ] : maskField; return modulopt.defaults[ option ]; } }