exec: remove duplicate code
[jimtcl.git] / jim-pack.c
blob0fdd445f31189a631575c800b557c3d55628c1cc
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 (Jim_GetWide(interp, argv[4], &width) != JIM_OK) {
297 return JIM_ERR;
300 if (option == OPT_STR) {
301 int len;
302 const char *str = Jim_GetString(argv[1], &len);
304 if (width % 8 || pos % 8) {
305 Jim_SetResultString(interp, "string field is not on a byte boundary", -1);
306 return JIM_ERR;
309 if (pos >= 0 && width > 0 && pos < len * 8) {
310 if (pos + width > len * 8) {
311 width = len * 8 - pos;
313 Jim_SetResultString(interp, str + pos / 8, width / 8);
315 return JIM_OK;
317 else {
318 int len;
319 const unsigned char *str = (const unsigned char *)Jim_GetString(argv[1], &len);
320 jim_wide result = 0;
322 if (width > sizeof(jim_wide) * 8) {
323 Jim_SetResultFormatted(interp, "int field is too wide: %#s", argv[4]);
324 return JIM_ERR;
327 if (pos >= 0 && width > 0 && pos < len * 8) {
328 if (pos + width > len * 8) {
329 width = len * 8 - pos;
331 if (option == OPT_INTBE || option == OPT_UINTBE || option == OPT_FLOATBE) {
332 result = JimBitIntBigEndian(str, pos, width);
334 else {
335 result = JimBitIntLittleEndian(str, pos, width);
337 if (option == OPT_INTBE || option == OPT_INTLE) {
338 result = JimSignExtend(result, width);
343 if (option == OPT_FLOATBE || option == OPT_FLOATLE) {
344 double fresult;
345 if (width == 32) {
346 fresult = (double) JimIntToFloat(result);
347 } else if (width == 64) {
348 fresult = JimIntToDouble(result);
349 } else {
350 Jim_SetResultFormatted(interp, "float field has bad bitwidth: %#s", argv[4]);
351 return JIM_ERR;
353 Jim_SetResult(interp, Jim_NewDoubleObj(interp, fresult));
354 } else {
355 Jim_SetResultInt(interp, result);
357 return JIM_OK;
362 * [pack]
364 * Usage: pack varname value -intbe|-intle|-floatle|-floatbe|-str width ?bitoffset?
366 * Packs the binary representation of 'value' into the variable of the given name.
367 * The value is packed according to the given type, width and bitoffset.
368 * The variable is created if necessary (like [append])
369 * Ihe variable is expanded if necessary
371 static int Jim_PackCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
373 int option;
374 static const char * const options[] = { "-intle", "-intbe", "-floatle", "-floatbe",
375 "-str", NULL };
376 enum { OPT_LE, OPT_BE, OPT_FLOATLE, OPT_FLOATBE, OPT_STR };
377 jim_wide pos = 0;
378 jim_wide width;
379 jim_wide value;
380 double fvalue;
381 Jim_Obj *stringObjPtr;
382 int len;
383 int freeobj = 0;
385 if (argc != 5 && argc != 6) {
386 Jim_WrongNumArgs(interp, 1, argv,
387 "varName value -intle|-intbe|-floatle|-floatbe|-str bitwidth ?bitoffset?");
388 return JIM_ERR;
390 if (Jim_GetEnum(interp, argv[3], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
391 return JIM_ERR;
393 if ((option == OPT_LE || option == OPT_BE) &&
394 Jim_GetWide(interp, argv[2], &value) != JIM_OK) {
395 return JIM_ERR;
397 if ((option == OPT_FLOATLE || option == OPT_FLOATBE) &&
398 Jim_GetDouble(interp, argv[2], &fvalue) != JIM_OK) {
399 return JIM_ERR;
401 if (Jim_GetWide(interp, argv[4], &width) != JIM_OK) {
402 return JIM_ERR;
404 if (width <= 0 || (option == OPT_STR && width % 8) || (option != OPT_STR && width > sizeof(jim_wide) * 8) ||
405 ((option == OPT_FLOATLE || option == OPT_FLOATBE) && width != 32 && width != 64)) {
406 Jim_SetResultFormatted(interp, "bad bitwidth: %#s", argv[4]);
407 return JIM_ERR;
409 if (argc == 6) {
410 if (Jim_GetWide(interp, argv[5], &pos) != JIM_OK) {
411 return JIM_ERR;
413 if (pos < 0 || (option == OPT_STR && pos % 8)) {
414 Jim_SetResultFormatted(interp, "bad bitoffset: %#s", argv[5]);
415 return JIM_ERR;
419 stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED);
420 if (!stringObjPtr) {
421 /* Create the string if it doesn't exist */
422 stringObjPtr = Jim_NewEmptyStringObj(interp);
423 freeobj = 1;
425 else if (Jim_IsShared(stringObjPtr)) {
426 freeobj = 1;
427 stringObjPtr = Jim_DuplicateObj(interp, stringObjPtr);
430 len = Jim_Length(stringObjPtr) * 8;
432 /* Extend the string as necessary first */
433 while (len < pos + width) {
434 Jim_AppendString(interp, stringObjPtr, "", 1);
435 len += 8;
438 Jim_SetResultInt(interp, pos + width);
440 /* Now set the bits. Note that the the string *must* have no non-string rep
441 * since we are writing the bytes directly.
443 Jim_AppendString(interp, stringObjPtr, "", 0);
445 /* Convert floating point to integer if necessary */
446 if (option == OPT_FLOATLE || option == OPT_FLOATBE) {
447 /* Note that the following is slightly incompatible with Tcl behaviour.
448 * In Tcl floating overflow gives FLT_MAX (cf. test binary-13.13).
449 * In Jim Tcl it gives Infinity. This behavior may change.
451 value = (width == 32) ? JimFloatToInt((float)fvalue) : JimDoubleToInt(fvalue);
454 if (option == OPT_BE || option == OPT_FLOATBE) {
455 JimSetBitsIntBigEndian((unsigned char *)stringObjPtr->bytes, value, pos, width);
457 else if (option == OPT_LE || option == OPT_FLOATLE) {
458 JimSetBitsIntLittleEndian((unsigned char *)stringObjPtr->bytes, value, pos, width);
460 else {
461 pos /= 8;
462 width /= 8;
464 if (width > Jim_Length(argv[2])) {
465 width = Jim_Length(argv[2]);
467 memcpy(stringObjPtr->bytes + pos, Jim_String(argv[2]), width);
468 /* No padding is needed since the string is already extended */
471 if (Jim_SetVariable(interp, argv[1], stringObjPtr) != JIM_OK) {
472 if (freeobj) {
473 Jim_FreeNewObj(interp, stringObjPtr);
474 return JIM_ERR;
477 return JIM_OK;
480 int Jim_packInit(Jim_Interp *interp)
482 if (Jim_PackageProvide(interp, "pack", "1.0", JIM_ERRMSG)) {
483 return JIM_ERR;
486 Jim_CreateCommand(interp, "unpack", Jim_UnpackCmd, NULL, NULL);
487 Jim_CreateCommand(interp, "pack", Jim_PackCmd, NULL, NULL);
488 return JIM_OK;