tests: jimsh.tests additional tests
[jimtcl.git] / jim-pack.c
blob65181928d9feceea637d28cce4669a13d85eae26
1 #include <string.h>
2 #include <jim.h>
4 /* Provides the [pack] and [unpack] commands to pack and unpack
5 * a binary string to/from arbitrary width integers and strings.
7 * This may be used to implement the [binary] command.
8 */
10 /**
11 * Big endian bit test.
13 * Considers 'bitvect' as a big endian bit stream and returns
14 * bit 'b' as zero or non-zero.
16 static int JimTestBitBigEndian(const unsigned char *bitvec, int b)
18 div_t pos = div(b, 8);
19 return bitvec[pos.quot] & (1 << (7 - pos.rem));
22 /**
23 * Little endian bit test.
25 * Considers 'bitvect' as a little endian bit stream and returns
26 * bit 'b' as zero or non-zero.
28 static int JimTestBitLittleEndian(const unsigned char *bitvec, int b)
30 div_t pos = div(b, 8);
31 return bitvec[pos.quot] & (1 << pos.rem);
34 /**
35 * Sign extends the given value, 'n' of width 'width' bits.
37 * For example, sign extending 0x80 with a width of 8, produces -128
39 static jim_wide JimSignExtend(jim_wide n, int width)
41 if (width == sizeof(jim_wide) * 8) {
42 /* Can't sign extend the maximum size integer */
43 return n;
45 if (n & ((jim_wide)1 << (width - 1))) {
46 /* Need to extend */
47 n -= ((jim_wide)1 << width);
50 return n;
53 /**
54 * Big endian integer extraction.
56 * Considers 'bitvect' as a big endian bit stream.
57 * Returns an integer of the given width (in bits)
58 * starting at the given position (in bits).
60 * The pos/width must represent bits inside bitvec,
61 * and the width be no more than the width of jim_wide.
63 static jim_wide JimBitIntBigEndian(const unsigned char *bitvec, int pos, int width)
65 jim_wide result = 0;
66 int i;
68 /* Aligned, byte extraction */
69 if (pos % 8 == 0 && width % 8 == 0) {
70 for (i = 0; i < width; i += 8) {
71 result = (result << 8) + bitvec[(pos + i) / 8];
73 return result;
76 /* Unaligned */
77 for (i = 0; i < width; i++) {
78 if (JimTestBitBigEndian(bitvec, pos + width - i - 1)) {
79 result |= ((jim_wide)1 << i);
83 return result;
86 /**
87 * Little endian integer extraction.
89 * Like JimBitIntBigEndian() but considers 'bitvect' as a little endian bit stream.
91 static jim_wide JimBitIntLittleEndian(const unsigned char *bitvec, int pos, int width)
93 jim_wide result = 0;
94 int i;
96 /* Aligned, byte extraction */
97 if (pos % 8 == 0 && width % 8 == 0) {
98 for (i = 0; i < width; i += 8) {
99 result += (jim_wide)bitvec[(pos + i) / 8] << i;
101 return result;
104 /* Unaligned */
105 for (i = 0; i < width; i++) {
106 if (JimTestBitLittleEndian(bitvec, pos + i)) {
107 result |= ((jim_wide)1 << i);
111 return result;
115 * Big endian bit set.
117 * Considers 'bitvect' as a big endian bit stream and sets
118 * bit 'b' to 'bit'
120 static void JimSetBitBigEndian(unsigned char *bitvec, int b, int bit)
122 div_t pos = div(b, 8);
123 if (bit) {
124 bitvec[pos.quot] |= (1 << (7 - pos.rem));
126 else {
127 bitvec[pos.quot] &= ~(1 << (7 - pos.rem));
132 * Little endian bit set.
134 * Considers 'bitvect' as a little endian bit stream and sets
135 * bit 'b' to 'bit'
137 static void JimSetBitLittleEndian(unsigned char *bitvec, int b, int bit)
139 div_t pos = div(b, 8);
140 if (bit) {
141 bitvec[pos.quot] |= (1 << pos.rem);
143 else {
144 bitvec[pos.quot] &= ~(1 << pos.rem);
149 * Big endian integer packing.
151 * Considers 'bitvect' as a big endian bit stream.
152 * Packs integer 'value' of the given width (in bits)
153 * starting at the given position (in bits).
155 * The pos/width must represent bits inside bitvec,
156 * and the width be no more than the width of jim_wide.
158 static void JimSetBitsIntBigEndian(unsigned char *bitvec, jim_wide value, int pos, int width)
160 int i;
162 /* Common fast option */
163 if (pos % 8 == 0 && width == 8) {
164 bitvec[pos / 8] = value;
165 return;
168 for (i = 0; i < width; i++) {
169 int bit = !!(value & ((jim_wide)1 << i));
170 JimSetBitBigEndian(bitvec, pos + width - i - 1, bit);
175 * Little endian version of JimSetBitsIntBigEndian()
177 static void JimSetBitsIntLittleEndian(unsigned char *bitvec, jim_wide value, int pos, int width)
179 int i;
181 /* Common fast option */
182 if (pos % 8 == 0 && width == 8) {
183 bitvec[pos / 8] = value;
184 return;
187 for (i = 0; i < width; i++) {
188 int bit = !!(value & ((jim_wide)1 << i));
189 JimSetBitLittleEndian(bitvec, pos + i, bit);
194 * Binary conversion of jim_wide integer to float
196 * Considers the least significant bits of
197 * jim_wide 'value' as a IEEE float.
199 * Should work for both little- and big-endian platforms.
201 static float JimIntToFloat(jim_wide value)
203 int offs;
204 float val;
206 /* Skip offs to get to least significant bytes */
207 offs = Jim_IsBigEndian() ? (sizeof(jim_wide) - sizeof(float)) : 0;
209 memcpy(&val, (unsigned char *) &value + offs, sizeof(float));
210 return val;
214 * Binary conversion of jim_wide integer to double
216 * Double precision version of JimIntToFloat
218 static double JimIntToDouble(jim_wide value)
220 int offs;
221 double val;
223 /* Skip offs to get to least significant bytes */
224 offs = Jim_IsBigEndian() ? (sizeof(jim_wide) - sizeof(double)) : 0;
226 memcpy(&val, (unsigned char *) &value + offs, sizeof(double));
227 return val;
231 * Binary conversion of float to jim_wide integer
233 * Considers the bits of IEEE float 'value' as integer.
234 * The integer is zero-extended to jim_wide.
236 * Should work for both little- and big-endian platforms.
238 static jim_wide JimFloatToInt(float value)
240 int offs;
241 jim_wide val = 0;
243 /* Skip offs to get to least significant bytes */
244 offs = Jim_IsBigEndian() ? (sizeof(jim_wide) - sizeof(float)) : 0;
246 memcpy((unsigned char *) &val + offs, &value, sizeof(float));
247 return val;
251 * Binary conversion of double to jim_wide integer
253 * Double precision version of JimFloatToInt
255 static jim_wide JimDoubleToInt(double value)
257 int offs;
258 jim_wide val = 0;
260 /* Skip offs to get to least significant bytes */
261 offs = Jim_IsBigEndian() ? (sizeof(jim_wide) - sizeof(double)) : 0;
263 memcpy((unsigned char *) &val + offs, &value, sizeof(double));
264 return val;
268 * [unpack]
270 * Usage: unpack binvalue -intbe|-intle|-uintbe|-uintle|-floatbe|-floatle|-str bitpos bitwidth
272 * Unpacks bits from $binvalue at bit position $bitpos and with $bitwidth.
273 * Interprets the value according to the type and returns it.
275 static int Jim_UnpackCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
277 int option;
278 static const char * const options[] = { "-intbe", "-intle", "-uintbe", "-uintle",
279 "-floatbe", "-floatle", "-str", NULL };
280 enum { OPT_INTBE, OPT_INTLE, OPT_UINTBE, OPT_UINTLE, OPT_FLOATBE, OPT_FLOATLE, OPT_STR, };
281 jim_wide pos;
282 jim_wide width;
284 if (argc != 5) {
285 Jim_WrongNumArgs(interp, 1, argv,
286 "binvalue -intbe|-intle|-uintbe|-uintle|-floatbe|-floatle|-str bitpos bitwidth");
287 return JIM_ERR;
289 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
290 return JIM_ERR;
293 if (Jim_GetWide(interp, argv[3], &pos) != JIM_OK) {
294 return JIM_ERR;
296 if (pos < 0 || (option == OPT_STR && pos % 8)) {
297 Jim_SetResultFormatted(interp, "bad bitoffset: %#s", argv[3]);
298 return JIM_ERR;
300 if (Jim_GetWide(interp, argv[4], &width) != JIM_OK) {
301 return JIM_ERR;
303 if (width < 0 || (option == OPT_STR && width % 8) || (option != OPT_STR && width > sizeof(jim_wide) * 8) ||
304 ((option == OPT_FLOATLE || option == OPT_FLOATBE) && width != 32 && width != 64)) {
305 Jim_SetResultFormatted(interp, "bad bitwidth: %#s", argv[4]);
306 return JIM_ERR;
309 if (option == OPT_STR) {
310 int len;
311 const char *str = Jim_GetString(argv[1], &len);
313 if (pos < len * 8) {
314 if (pos + width > len * 8) {
315 width = len * 8 - pos;
317 Jim_SetResultString(interp, str + pos / 8, width / 8);
319 return JIM_OK;
321 else {
322 int len;
323 const unsigned char *str = (const unsigned char *)Jim_GetString(argv[1], &len);
324 jim_wide result = 0;
326 if (pos < len * 8) {
327 if (pos + width > len * 8) {
328 width = len * 8 - pos;
330 if (option == OPT_INTBE || option == OPT_UINTBE || option == OPT_FLOATBE) {
331 result = JimBitIntBigEndian(str, pos, width);
333 else {
334 result = JimBitIntLittleEndian(str, pos, width);
336 if (option == OPT_INTBE || option == OPT_INTLE) {
337 result = JimSignExtend(result, width);
342 if (option == OPT_FLOATBE || option == OPT_FLOATLE) {
343 double fresult;
344 if (width == 32) {
345 fresult = (double) JimIntToFloat(result);
346 } else {
347 fresult = JimIntToDouble(result);
349 Jim_SetResult(interp, Jim_NewDoubleObj(interp, fresult));
350 } else {
351 Jim_SetResultInt(interp, result);
353 return JIM_OK;
358 * [pack]
360 * Usage: pack varname value -intbe|-intle|-floatle|-floatbe|-str width ?bitoffset?
362 * Packs the binary representation of 'value' into the variable of the given name.
363 * The value is packed according to the given type, width and bitoffset.
364 * The variable is created if necessary (like [append])
365 * The variable is expanded if necessary
367 static int Jim_PackCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
369 int option;
370 static const char * const options[] = { "-intle", "-intbe", "-floatle", "-floatbe",
371 "-str", NULL };
372 enum { OPT_LE, OPT_BE, OPT_FLOATLE, OPT_FLOATBE, OPT_STR };
373 jim_wide pos = 0;
374 jim_wide width;
375 jim_wide value;
376 double fvalue;
377 Jim_Obj *stringObjPtr;
378 int len;
379 int freeobj = 0;
381 if (argc != 5 && argc != 6) {
382 Jim_WrongNumArgs(interp, 1, argv,
383 "varName value -intle|-intbe|-floatle|-floatbe|-str bitwidth ?bitoffset?");
384 return JIM_ERR;
386 if (Jim_GetEnum(interp, argv[3], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
387 return JIM_ERR;
389 if ((option == OPT_LE || option == OPT_BE) &&
390 Jim_GetWide(interp, argv[2], &value) != JIM_OK) {
391 return JIM_ERR;
393 if ((option == OPT_FLOATLE || option == OPT_FLOATBE) &&
394 Jim_GetDouble(interp, argv[2], &fvalue) != JIM_OK) {
395 return JIM_ERR;
397 if (Jim_GetWide(interp, argv[4], &width) != JIM_OK) {
398 return JIM_ERR;
400 if (width <= 0 || (option == OPT_STR && width % 8) || (option != OPT_STR && width > sizeof(jim_wide) * 8) ||
401 ((option == OPT_FLOATLE || option == OPT_FLOATBE) && width != 32 && width != 64)) {
402 Jim_SetResultFormatted(interp, "bad bitwidth: %#s", argv[4]);
403 return JIM_ERR;
405 if (argc == 6) {
406 if (Jim_GetWide(interp, argv[5], &pos) != JIM_OK) {
407 return JIM_ERR;
409 if (pos < 0 || (option == OPT_STR && pos % 8)) {
410 Jim_SetResultFormatted(interp, "bad bitoffset: %#s", argv[5]);
411 return JIM_ERR;
415 stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED);
416 if (!stringObjPtr) {
417 /* Create the string if it doesn't exist */
418 stringObjPtr = Jim_NewEmptyStringObj(interp);
419 freeobj = 1;
421 else if (Jim_IsShared(stringObjPtr)) {
422 freeobj = 1;
423 stringObjPtr = Jim_DuplicateObj(interp, stringObjPtr);
426 len = Jim_Length(stringObjPtr) * 8;
428 /* Extend the string as necessary first */
429 while (len < pos + width) {
430 Jim_AppendString(interp, stringObjPtr, "", 1);
431 len += 8;
434 Jim_SetResultInt(interp, pos + width);
436 /* Now set the bits. Note that the the string *must* have no non-string rep
437 * since we are writing the bytes directly.
439 Jim_AppendString(interp, stringObjPtr, "", 0);
441 /* Convert floating point to integer if necessary */
442 if (option == OPT_FLOATLE || option == OPT_FLOATBE) {
443 /* Note that the following is slightly incompatible with Tcl behaviour.
444 * In Tcl floating overflow gives FLT_MAX (cf. test binary-13.13).
445 * In Jim Tcl it gives Infinity. This behavior may change.
447 value = (width == 32) ? JimFloatToInt((float)fvalue) : JimDoubleToInt(fvalue);
450 if (option == OPT_BE || option == OPT_FLOATBE) {
451 JimSetBitsIntBigEndian((unsigned char *)stringObjPtr->bytes, value, pos, width);
453 else if (option == OPT_LE || option == OPT_FLOATLE) {
454 JimSetBitsIntLittleEndian((unsigned char *)stringObjPtr->bytes, value, pos, width);
456 else {
457 pos /= 8;
458 width /= 8;
460 if (width > Jim_Length(argv[2])) {
461 width = Jim_Length(argv[2]);
463 memcpy(stringObjPtr->bytes + pos, Jim_String(argv[2]), width);
464 /* No padding is needed since the string is already extended */
467 if (Jim_SetVariable(interp, argv[1], stringObjPtr) != JIM_OK) {
468 if (freeobj) {
469 Jim_FreeNewObj(interp, stringObjPtr);
470 return JIM_ERR;
473 return JIM_OK;
476 int Jim_packInit(Jim_Interp *interp)
478 if (Jim_PackageProvide(interp, "pack", "1.0", JIM_ERRMSG)) {
479 return JIM_ERR;
482 Jim_CreateCommand(interp, "unpack", Jim_UnpackCmd, NULL, NULL);
483 Jim_CreateCommand(interp, "pack", Jim_PackCmd, NULL, NULL);
484 return JIM_OK;