diff options
author | Steve Bennett <steveb@workware.net.au> | 2011-04-21 15:13:12 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2011-06-10 14:00:32 +1000 |
commit | a7f2bb4a3504802f41456944ee3fddffaa646cd5 (patch) | |
tree | a75be1a2ae2768519d89547bd73d7cca914b4a35 /jim-pack.c | |
parent | 9290c4ae2dcb84c12903938eaa993a335f48de07 (diff) | |
download | jimtcl-a7f2bb4a3504802f41456944ee3fddffaa646cd5.zip jimtcl-a7f2bb4a3504802f41456944ee3fddffaa646cd5.tar.gz jimtcl-a7f2bb4a3504802f41456944ee3fddffaa646cd5.tar.bz2 |
Implement the [binary] command
Supports everything except floating point types
binary is implemented in Tcl on top of the low level [pack] and [unpack] commands
Signed-off-by: Steve Bennett <steveb@workware.net.au>
Diffstat (limited to 'jim-pack.c')
-rw-r--r-- | jim-pack.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/jim-pack.c b/jim-pack.c new file mode 100644 index 0000000..f517a92 --- /dev/null +++ b/jim-pack.c @@ -0,0 +1,380 @@ +#include <string.h> +#include <jim.h> + +/* Provides the [pack] and [unpack] commands to pack and unpack + * a binary string to/from arbitrary width integers and strings. + * + * This may be used to implement the [binary] command. + */ + +/** + * Big endian bit test. + * + * Considers 'bitvect' as a big endian bit stream and returns + * bit 'b' as zero or non-zero. + */ +static int JimTestBitBigEndian(const unsigned char *bitvec, int b) +{ + div_t pos = div(b, 8); + return bitvec[pos.quot] & (1 << (7 - pos.rem)); +} + +/** + * Little endian bit test. + * + * Considers 'bitvect' as a little endian bit stream and returns + * bit 'b' as zero or non-zero. + */ +static int JimTestBitLittleEndian(const unsigned char *bitvec, int b) +{ + div_t pos = div(b, 8); + return bitvec[pos.quot] & (1 << pos.rem); +} + +/** + * Sign extends the given value, 'n' of width 'width' bits. + * + * For example, sign extending 0x80 with a width of 8, produces -128 + */ +static jim_wide JimSignExtend(jim_wide n, int width) +{ + if (width == sizeof(jim_wide) * 8) { + /* Can't sign extend the maximum size integer */ + return n; + } + if (n & ((jim_wide)1 << (width - 1))) { + /* Need to extend */ + n -= ((jim_wide)1 << width); + } + + return n; +} + +/** + * Big endian integer extraction. + * + * Considers 'bitvect' as a big endian bit stream. + * Returns an integer of the given width (in bits) + * starting at the given position (in bits). + * + * The pos/width must represent bits inside bitvec, + * and the width be no more than the width of jim_wide. + */ +static jim_wide JimBitIntBigEndian(const unsigned char *bitvec, int pos, int width) +{ + jim_wide result = 0; + int i; + + /* Aligned, byte extraction */ + if (pos % 8 == 0 && width % 8 == 0) { + for (i = 0; i < width; i += 8) { + result = (result << 8) + bitvec[(pos + i) / 8]; + } + return result; + } + + /* Unaligned */ + for (i = 0; i < width; i++) { + if (JimTestBitBigEndian(bitvec, pos + width - i - 1)) { + result |= ((jim_wide)1 << i); + } + } + + return result; +} + +/** + * Little endian integer extraction. + * + * Like JimBitIntBigEndian() but considers 'bitvect' as a little endian bit stream. + */ +static jim_wide JimBitIntLittleEndian(const unsigned char *bitvec, int pos, int width) +{ + jim_wide result = 0; + int i; + + /* Aligned, byte extraction */ + if (pos % 8 == 0 && width % 8 == 0) { + for (i = 0; i < width; i += 8) { + result += (jim_wide)bitvec[(pos + i) / 8] << i; + } + return result; + } + + /* Unaligned */ + for (i = 0; i < width; i++) { + if (JimTestBitLittleEndian(bitvec, pos + i)) { + result |= ((jim_wide)1 << i); + } + } + + return result; +} + +/** + * Big endian bit set. + * + * Considers 'bitvect' as a big endian bit stream and sets + * bit 'b' to 'bit' + */ +static void JimSetBitBigEndian(unsigned char *bitvec, int b, int bit) +{ + div_t pos = div(b, 8); + if (bit) { + bitvec[pos.quot] |= (1 << (7 - pos.rem)); + } + else { + bitvec[pos.quot] &= ~(1 << (7 - pos.rem)); + } +} + +/** + * Little endian bit set. + * + * Considers 'bitvect' as a little endian bit stream and sets + * bit 'b' to 'bit' + */ +static void JimSetBitLittleEndian(unsigned char *bitvec, int b, int bit) +{ + div_t pos = div(b, 8); + if (bit) { + bitvec[pos.quot] |= (1 << pos.rem); + } + else { + bitvec[pos.quot] &= ~(1 << pos.rem); + } +} + +/** + * Big endian integer packing. + * + * Considers 'bitvect' as a big endian bit stream. + * Packs integer 'value' of the given width (in bits) + * starting at the given position (in bits). + * + * The pos/width must represent bits inside bitvec, + * and the width be no more than the width of jim_wide. + */ +static void JimSetBitsIntBigEndian(unsigned char *bitvec, jim_wide value, int pos, int width) +{ + int i; + + /* Common fast option */ + if (pos % 8 == 0 && width == 8) { + bitvec[pos / 8] = value; + return; + } + + for (i = 0; i < width; i++) { + int bit = !!(value & ((jim_wide)1 << i)); + JimSetBitBigEndian(bitvec, pos + width - i - 1, bit); + } +} + +/** + * Little endian version of JimSetBitsIntBigEndian() + */ +static void JimSetBitsIntLittleEndian(unsigned char *bitvec, jim_wide value, int pos, int width) +{ + int i; + + /* Common fast option */ + if (pos % 8 == 0 && width == 8) { + bitvec[pos / 8] = value; + return; + } + + for (i = 0; i < width; i++) { + int bit = !!(value & ((jim_wide)1 << i)); + JimSetBitLittleEndian(bitvec, pos + i, bit); + } +} + +/** + * [unpack] + * + * Usage: unpack binvalue -intbe|-intle|-uintbe|-uintle|-str bitpos bitwidth + * + * Unpacks bits from $binvalue at bit position $bitpos and with $bitwidth. + * Interprets the value according to the type and returns it. + */ +static int Jim_UnpackCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int option; + static const char const *options[] = { "-intbe", "-intle", "-uintbe", "-uintle", "-str", NULL }; + enum { OPT_INTBE, OPT_INTLE, OPT_UINTBE, OPT_UINTLE, OPT_STR, }; + jim_wide pos; + jim_wide width; + + if (argc != 5) { + Jim_WrongNumArgs(interp, 1, argv, "binvalue -intbe|-intle|-uintbe|-uintle|-str bitpos bitwidth"); + return JIM_ERR; + } + if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + + if (Jim_GetWide(interp, argv[3], &pos) != JIM_OK) { + return JIM_ERR; + } + if (Jim_GetWide(interp, argv[4], &width) != JIM_OK) { + return JIM_ERR; + } + + if (option == OPT_STR) { + int len; + const char *str = Jim_GetString(argv[1], &len); + + if (width % 8 || pos % 8) { + Jim_SetResultString(interp, "string field is not on a byte boundary", -1); + return JIM_ERR; + } + + if (pos >= 0 && width > 0 && pos < len * 8) { + if (pos + width > len * 8) { + width = len * 8 - pos; + } + Jim_SetResultString(interp, str + pos / 8, width / 8); + } + return JIM_OK; + } + else { + int len; + const unsigned char *str = (const unsigned char *)Jim_GetString(argv[1], &len); + jim_wide result = 0; + + if (width > sizeof(jim_wide) * 8) { + Jim_SetResultFormatted(interp, "int field is too wide: %#s", argv[4]); + return JIM_ERR; + } + + if (pos >= 0 && width > 0 && pos < len * 8) { + if (pos + width > len * 8) { + width = len * 8 - pos; + } + if (option == OPT_INTBE || option == OPT_UINTBE) { + result = JimBitIntBigEndian(str, pos, width); + } + else { + result = JimBitIntLittleEndian(str, pos, width); + } + if (option == OPT_INTBE || option == OPT_INTLE) { + result = JimSignExtend(result, width); + } + } + Jim_SetResultInt(interp, result); + return JIM_OK; + } +} + +/** + * [pack] + * + * Usage: pack varname value -intle|-intbe|-str width ?bitoffset? + * + * Packs the binary representation of 'value' into the variable of the given name. + * The value is packed according to the given type, width and bitoffset. + * The variable is created if necessary (like [append]) + * Ihe variable is expanded if necessary + */ +static int Jim_PackCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int option; + static const char const *options[] = { "-intle", "-intbe", "-str", NULL }; + enum { OPT_LE, OPT_BE, OPT_STR }; + jim_wide pos = 0; + jim_wide width; + jim_wide value; + Jim_Obj *stringObjPtr; + int len; + int freeobj = 0; + + if (argc != 5 && argc != 6) { + Jim_WrongNumArgs(interp, 1, argv, "varName value -intle|-intbe|-str bitwidth ?bitoffset?"); + return JIM_ERR; + } + if (Jim_GetEnum(interp, argv[3], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + if (option != OPT_STR && Jim_GetWide(interp, argv[2], &value) != JIM_OK) { + return JIM_ERR; + } + if (Jim_GetWide(interp, argv[4], &width) != JIM_OK) { + return JIM_ERR; + } + if (width <= 0 || (option == OPT_STR && width % 8) || (option != OPT_STR && width > sizeof(jim_wide) * 8)) { + Jim_SetResultFormatted(interp, "bad bitwidth: %#s", argv[5]); + return JIM_ERR; + } + if (argc == 6) { + if (Jim_GetWide(interp, argv[5], &pos) != JIM_OK) { + return JIM_ERR; + } + if (pos < 0 || (option == OPT_STR && pos % 8)) { + Jim_SetResultFormatted(interp, "bad bitoffset: %#s", argv[5]); + return JIM_ERR; + } + } + + stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED); + if (!stringObjPtr) { + /* Create the string if it doesn't exist */ + stringObjPtr = Jim_NewEmptyStringObj(interp); + freeobj = 1; + } + else if (Jim_IsShared(stringObjPtr)) { + freeobj = 1; + stringObjPtr = Jim_DuplicateObj(interp, stringObjPtr); + } + + len = Jim_Length(stringObjPtr) * 8; + + /* Extend the string as necessary first */ + while (len < pos + width) { + Jim_AppendString(interp, stringObjPtr, "", 1); + len += 8; + } + + Jim_SetResultInt(interp, pos + width); + + /* Now set the bits. Note that the the string *must* have no non-string rep + * since we are writing the bytes directly. + */ + Jim_AppendString(interp, stringObjPtr, "", 0); + + if (option == OPT_BE) { + JimSetBitsIntBigEndian((unsigned char *)stringObjPtr->bytes, value, pos, width); + } + else if (option == OPT_LE) { + JimSetBitsIntLittleEndian((unsigned char *)stringObjPtr->bytes, value, pos, width); + } + else { + pos /= 8; + width /= 8; + + if (width > Jim_Length(argv[2])) { + width = Jim_Length(argv[2]); + } + memcpy(stringObjPtr->bytes + pos, Jim_GetString(argv[2], NULL), width); + /* No padding is needed since the string is already extended */ + } + + if (Jim_SetVariable(interp, argv[1], stringObjPtr) != JIM_OK) { + if (freeobj) { + Jim_FreeNewObj(interp, stringObjPtr); + return JIM_ERR; + } + } + return JIM_OK; +} + +int Jim_packInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "pack", "1.0", JIM_ERRMSG)) { + return JIM_ERR; + } + + Jim_CreateCommand(interp, "unpack", Jim_UnpackCmd, NULL, NULL); + Jim_CreateCommand(interp, "pack", Jim_PackCmd, NULL, NULL); + return JIM_OK; +} |