@@ -211,6 +211,21 @@ type BitSizes =
211211 | 31
212212 | 32 ;
213213
214+ const TYPE_RANGES : {
215+ [ type : string ] : { min : number | bigint ; max : number | bigint } ;
216+ } = {
217+ Int8 : { min : - 128 , max : 127 } ,
218+ Int16 : { min : - 32768 , max : 32767 } ,
219+ Int32 : { min : - 2147483648 , max : 2147483647 } ,
220+ Uint8 : { min : 0 , max : 255 } ,
221+ Uint16 : { min : 0 , max : 65535 } ,
222+ Uint32 : { min : 0 , max : 4294967295 } ,
223+ BigInt64 : { min : - 9223372036854775808n , max : 9223372036854775807n } ,
224+ BigUint64 : { min : 0n , max : 18446744073709551615n } ,
225+ Float32 : { min : - 3.4e38 , max : 3.4e38 } ,
226+ Float64 : { min : - 1.8e308 , max : 1.8e308 } ,
227+ } ;
228+
214229const PRIMITIVE_SIZES : { [ key in PrimitiveTypes ] : number } = {
215230 uint8 : 1 ,
216231 uint16le : 2 ,
@@ -706,7 +721,7 @@ export class Parser {
706721 }
707722
708723 pointer ( varName : string , options : ParserOptions ) : this {
709- if ( options . offset == null ) {
724+ if ( ! options . offset ) {
710725 throw new Error ( "offset is required for pointer." ) ;
711726 }
712727
@@ -897,7 +912,25 @@ export class Parser {
897912 return size ;
898913 }
899914
900- // Follow the parser chain till the root and start parsing from there
915+ /**
916+ * Parse the given buffer and return the parsed object
917+ *
918+ * @param buffer - the buffer to parse
919+ * @returns the parsed object
920+ *
921+ * @example
922+ * const parser = new Parser()
923+ * .endianess('little')
924+ * .uint16('num1')
925+ * .uint16('num2');
926+ *
927+ * const buf = Buffer.from([0x01, 0x02, 0x03, 0x04]);
928+ * parser.parse(buf); // => { num1: 513, num2: 1027 }
929+ *
930+ * @throws {Error } - Throws an error if the buffer is not a buffer
931+ * @throws {Error } - Throws an error if the buffer is too small
932+ */
933+
901934 parse ( buffer : Buffer | Uint8Array ) {
902935 if ( ! this . compiled ) {
903936 this . compile ( ) ;
@@ -906,6 +939,244 @@ export class Parser {
906939 return this . compiled ! ( buffer , this . constructorFn ) ;
907940 }
908941
942+ private encodeValue (
943+ dataView : DataView ,
944+ object : any ,
945+ offset : number = 0 ,
946+ ) : number {
947+ if ( this . varName && ! object . hasOwnProperty ( this . varName ) ) {
948+ throw new Error ( `Field "${ this . varName } " not present` ) ;
949+ }
950+
951+ if ( Object . keys ( PRIMITIVE_SIZES ) . indexOf ( this . type as string ) >= 0 ) {
952+ const val = object [ this . varName ] ;
953+ const size = PRIMITIVE_SIZES [ this . type as PrimitiveTypes ] ;
954+ const littleEndian =
955+ PRIMITIVE_LITTLE_ENDIANS [ this . type as PrimitiveTypes ] ;
956+ const typeName = PRIMITIVE_NAMES [ this . type as PrimitiveTypes ] ;
957+
958+ const range = TYPE_RANGES [ typeName ] ;
959+ const value = typeName . startsWith ( "Big" ) ? BigInt ( val ) : val ;
960+
961+ if ( value < range . min || value > range . max ) {
962+ throw new Error (
963+ `Field "${ this . varName } " should be a ${ typeName } and should be between ${ range . min } and ${ range . max } ` ,
964+ ) ;
965+ }
966+
967+ switch ( typeName ) {
968+ case "Int8" :
969+ dataView . setInt8 ( offset , val ) ;
970+ break ;
971+ case "Int16" :
972+ dataView . setInt16 ( offset , val , littleEndian ) ;
973+ break ;
974+ case "Int32" :
975+ dataView . setInt32 ( offset , val , littleEndian ) ;
976+ break ;
977+ case "Uint8" :
978+ dataView . setUint8 ( offset , val ) ;
979+ break ;
980+ case "Uint16" :
981+ dataView . setUint16 ( offset , val , littleEndian ) ;
982+ break ;
983+ case "Uint32" :
984+ dataView . setUint32 ( offset , val , littleEndian ) ;
985+ break ;
986+ case "Float32" :
987+ dataView . setFloat32 ( offset , val , littleEndian ) ;
988+ break ;
989+ case "Float64" :
990+ dataView . setFloat64 ( offset , val , littleEndian ) ;
991+ break ;
992+ case "BigInt64" :
993+ dataView . setBigInt64 ( offset , BigInt ( val ) , littleEndian ) ;
994+ break ;
995+ case "BigUint64" :
996+ dataView . setBigUint64 ( offset , BigInt ( val ) , littleEndian ) ;
997+ break ;
998+ }
999+
1000+ if ( this . next ) {
1001+ return this . next . encodeValue ( dataView , object , offset + size ) ;
1002+ }
1003+
1004+ return offset + size ;
1005+ }
1006+
1007+ if ( this . type === "bit" ) {
1008+ const val = object [ this . varName ] ;
1009+ const size = this . options . length as number ;
1010+
1011+ if ( typeof val !== "string" ) {
1012+ throw new Error ( "Bit field should be a string" ) ;
1013+ }
1014+
1015+ for ( let i = 0 ; i < size ; i ++ ) {
1016+ dataView . setUint8 ( offset + i , val . charCodeAt ( i ) ) ;
1017+ }
1018+
1019+ if ( this . next ) {
1020+ return this . next . encodeValue ( dataView , object , offset + size ) ;
1021+ }
1022+
1023+ return offset + size ;
1024+ }
1025+
1026+ if ( this . type === "string" ) {
1027+ const val = object [ this . varName ] ;
1028+ const textEncoder = new TextEncoder ( ) ;
1029+ const buffer = textEncoder . encode ( val ) ;
1030+
1031+ let length = buffer . length ;
1032+ if ( typeof this . options . length === "number" ) {
1033+ length = Math . min ( length , this . options . length ) ;
1034+ }
1035+
1036+ new Uint8Array ( dataView . buffer , offset , length ) . set (
1037+ buffer . subarray ( 0 , length ) ,
1038+ ) ;
1039+
1040+ if ( this . options . zeroTerminated ) {
1041+ dataView . setUint8 ( offset + length , 0 ) ;
1042+ length ++ ;
1043+ }
1044+ const newOffset = offset + length ;
1045+ if ( this . next ) {
1046+ return this . next . encodeValue ( dataView , object , newOffset ) ;
1047+ }
1048+ return newOffset ;
1049+ } else if ( this . type === "array" ) {
1050+ const arr = object [ this . varName ] ;
1051+ let newOffset = offset ;
1052+
1053+ for ( const item of arr ) {
1054+ if ( typeof this . options . type === "string" ) {
1055+ const parser = new Parser ( ) . primitiveN (
1056+ this . options . type as PrimitiveTypes ,
1057+ "value" ,
1058+ { } ,
1059+ ) ;
1060+ newOffset = parser . encodeValue ( dataView , { value : item } , newOffset ) ;
1061+ } else if ( this . options . type instanceof Parser ) {
1062+ newOffset = this . options . type . encodeValue ( dataView , item , newOffset ) ;
1063+ }
1064+ }
1065+
1066+ if ( this . next ) {
1067+ return this . next . encodeValue ( dataView , object , newOffset ) ;
1068+ }
1069+ return newOffset ;
1070+ }
1071+
1072+ if ( this . type === "nest" ) {
1073+ const val = object [ this . varName ] ;
1074+ if ( this . options . type instanceof Parser ) {
1075+ return this . options . type . encodeValue ( dataView , val , offset ) ;
1076+ }
1077+ }
1078+
1079+ if ( this . next ) {
1080+ return this . next . encodeValue ( dataView , object , offset ) ;
1081+ }
1082+
1083+ return offset ;
1084+ }
1085+
1086+ calculateSize ( object : any ) : number {
1087+ if ( ! this . type && ! this . next ) return 0 ;
1088+
1089+ let size = 0 ;
1090+
1091+ if ( this . type ) {
1092+ if ( Object . keys ( PRIMITIVE_SIZES ) . indexOf ( this . type ) >= 0 ) {
1093+ size += PRIMITIVE_SIZES [ this . type as PrimitiveTypes ] ;
1094+ } else if ( this . type === "bit" ) {
1095+ size += this . options . length as number ;
1096+ } else if ( this . type === "string" ) {
1097+ const val = object [ this . varName ] ;
1098+ if ( val ) {
1099+ const textEncoder = new TextEncoder ( ) ;
1100+ const encoded = textEncoder . encode ( val ) ;
1101+
1102+ if ( typeof this . options . length === "number" ) {
1103+ size += Math . min ( encoded . length , this . options . length ) ;
1104+ } else if ( this . options . zeroTerminated ) {
1105+ size += encoded . length + 1 ; // +1 for null terminator
1106+ } else {
1107+ size += encoded . length ;
1108+ }
1109+ }
1110+ } else if ( this . type === "array" ) {
1111+ const arr = object [ this . varName ] ;
1112+ if ( arr && Array . isArray ( arr ) ) {
1113+ if ( typeof this . options . type === "string" ) {
1114+ size +=
1115+ arr . length * PRIMITIVE_SIZES [ this . options . type as PrimitiveTypes ] ;
1116+ } else if ( this . options . type instanceof Parser ) {
1117+ size += arr . reduce (
1118+ ( sum , item ) =>
1119+ sum +
1120+ ( this . options . type instanceof Parser
1121+ ? this . options . type . calculateSize ( item )
1122+ : 0 ) ,
1123+ 0 ,
1124+ ) ;
1125+ }
1126+ }
1127+ } else if ( this . type === "nest" ) {
1128+ const val = object [ this . varName ] ;
1129+ if ( val && this . options . type instanceof Parser ) {
1130+ size += this . options . type . calculateSize ( val ) ;
1131+ }
1132+ }
1133+ }
1134+
1135+ if ( this . next ) {
1136+ size += this . next . calculateSize ( object ) ;
1137+ }
1138+
1139+ return size ;
1140+ }
1141+
1142+ /**
1143+ * Encodes a JavaScript object into a binary buffer according to the parser's specification.
1144+ *
1145+ * @param {object } data - The object to encode
1146+ * @returns {Buffer } The encoded binary data
1147+ *
1148+ * @example
1149+ * // Basic usage with primitive types
1150+ * const parser = new Parser()
1151+ * .int8('age')
1152+ * .string('name', { zeroTerminated: true });
1153+ *
1154+ * const buffer = parser.encode({
1155+ * age: 25,
1156+ * name: 'John'
1157+ * });
1158+ *
1159+ * console.log(buffer); // <Buffer 19 4a 6f 68 6e 00>
1160+ *
1161+ * @throws {Error } When a required field is missing from the input object
1162+ * @throws {Error } When field values don't match their type constraints
1163+ * @throws {Error } When an integer overflows or underflows
1164+ */
1165+ encode ( data : any ) : Buffer {
1166+ const size = this . calculateSize ( data ) ;
1167+
1168+ const buffer = Buffer . alloc ( size ) ;
1169+ const dataView = new DataView (
1170+ buffer . buffer ,
1171+ buffer . byteOffset ,
1172+ buffer . length ,
1173+ ) ;
1174+
1175+ this . encodeValue ( dataView , data ) ;
1176+
1177+ return buffer ;
1178+ }
1179+
9091180 private setNextParser (
9101181 type : Types ,
9111182 varName : string ,
0 commit comments