BCD (Binary Coded Decimals): Nim parsing library

BCD (Binary Coded Decimals) is a common way to encode integer numbers in a way that makes human-readable output somewhat simpler. In this encoding scheme, every decimal digit is encoded as either a single byte (8 bits), or a nibble (half of a byte, 4 bits). This obviously wastes a lot of bits, but it makes translation into human-readable string much easier than traditional binary-to-decimal conversion process, which includes lots of divisions by 10.

For example, encoding integer 31337 in 8-digit, 8 bits per digit, big endian order of digits BCD format yields

00 00 00 03 01 03 03 07

Encoding the same integer as 8-digit, 4 bits per digit, little endian order BCD format would yield:

73 31 30 00

Using this type of encoding in Kaitai Struct is pretty straightforward: one calls for this type, specifying desired encoding parameters, and gets result using either as_int or as_str attributes.

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.8

References

This page hosts a formal specification of BCD (Binary Coded Decimals) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse BCD (Binary Coded Decimals)

bcd.nim

import kaitai_struct_nim_runtime
import options

type
  Bcd* = ref object of KaitaiStruct
    `digits`*: seq[int]
    `numDigits`*: uint8
    `bitsPerDigit`*: uint8
    `isLe`*: bool
    `parent`*: KaitaiStruct
    `asIntInst`: int
    `asIntInstFlag`: bool
    `asIntLeInst`: int
    `asIntLeInstFlag`: bool
    `lastIdxInst`: int
    `lastIdxInstFlag`: bool
    `asIntBeInst`: int
    `asIntBeInstFlag`: bool

proc read*(_: typedesc[Bcd], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct, numDigits: any, bitsPerDigit: any, isLe: any): Bcd

proc asInt*(this: Bcd): int
proc asIntLe*(this: Bcd): int
proc lastIdx*(this: Bcd): int
proc asIntBe*(this: Bcd): int


##[
BCD (Binary Coded Decimals) is a common way to encode integer
numbers in a way that makes human-readable output somewhat
simpler. In this encoding scheme, every decimal digit is encoded as
either a single byte (8 bits), or a nibble (half of a byte, 4
bits). This obviously wastes a lot of bits, but it makes translation
into human-readable string much easier than traditional
binary-to-decimal conversion process, which includes lots of
divisions by 10.

For example, encoding integer 31337 in 8-digit, 8 bits per digit,
big endian order of digits BCD format yields

```
00 00 00 03 01 03 03 07
```

Encoding the same integer as 8-digit, 4 bits per digit, little
endian order BCD format would yield:

```
73 31 30 00
```

Using this type of encoding in Kaitai Struct is pretty
straightforward: one calls for this type, specifying desired
encoding parameters, and gets result using either `as_int` or
`as_str` attributes.

]##
proc read*(_: typedesc[Bcd], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct, numDigits: any, bitsPerDigit: any, isLe: any): Bcd =
  template this: untyped = result
  this = new(Bcd)
  let root = if root == nil: cast[Bcd](this) else: cast[Bcd](root)
  this.io = io
  this.root = root
  this.parent = parent
  let numDigitsExpr = uint8(numDigits)
  this.numDigits = numDigitsExpr
  let bitsPerDigitExpr = uint8(bitsPerDigit)
  this.bitsPerDigit = bitsPerDigitExpr
  let isLeExpr = bool(isLe)
  this.isLe = isLeExpr

  for i in 0 ..< int(this.numDigits):
    block:
      let on = this.bitsPerDigit
      if on == 4:
        let it = int(this.io.readBitsIntBe(4))
        this.digits.add(it)
      elif on == 8:
        let it = int(this.io.readU1())
        this.digits.add(it)

proc asInt(this: Bcd): int = 

  ##[
  Value of this BCD number as integer. Endianness would be selected based on `is_le` parameter given.
  ]##
  if this.asIntInstFlag:
    return this.asIntInst
  let asIntInstExpr = int((if this.isLe: this.asIntLe else: this.asIntBe))
  this.asIntInst = asIntInstExpr
  this.asIntInstFlag = true
  return this.asIntInst

proc asIntLe(this: Bcd): int = 

  ##[
  Value of this BCD number as integer (treating digit order as little-endian).
  ]##
  if this.asIntLeInstFlag:
    return this.asIntLeInst
  let asIntLeInstExpr = int((this.digits[0] + (if this.numDigits < 2: 0 else: ((this.digits[1] * 10) + (if this.numDigits < 3: 0 else: ((this.digits[2] * 100) + (if this.numDigits < 4: 0 else: ((this.digits[3] * 1000) + (if this.numDigits < 5: 0 else: ((this.digits[4] * 10000) + (if this.numDigits < 6: 0 else: ((this.digits[5] * 100000) + (if this.numDigits < 7: 0 else: ((this.digits[6] * 1000000) + (if this.numDigits < 8: 0 else: (this.digits[7] * 10000000))))))))))))))))
  this.asIntLeInst = asIntLeInstExpr
  this.asIntLeInstFlag = true
  return this.asIntLeInst

proc lastIdx(this: Bcd): int = 

  ##[
  Index of last digit (0-based).
  ]##
  if this.lastIdxInstFlag:
    return this.lastIdxInst
  let lastIdxInstExpr = int((this.numDigits - 1))
  this.lastIdxInst = lastIdxInstExpr
  this.lastIdxInstFlag = true
  return this.lastIdxInst

proc asIntBe(this: Bcd): int = 

  ##[
  Value of this BCD number as integer (treating digit order as big-endian).
  ]##
  if this.asIntBeInstFlag:
    return this.asIntBeInst
  let asIntBeInstExpr = int((this.digits[this.lastIdx] + (if this.numDigits < 2: 0 else: ((this.digits[(this.lastIdx - 1)] * 10) + (if this.numDigits < 3: 0 else: ((this.digits[(this.lastIdx - 2)] * 100) + (if this.numDigits < 4: 0 else: ((this.digits[(this.lastIdx - 3)] * 1000) + (if this.numDigits < 5: 0 else: ((this.digits[(this.lastIdx - 4)] * 10000) + (if this.numDigits < 6: 0 else: ((this.digits[(this.lastIdx - 5)] * 100000) + (if this.numDigits < 7: 0 else: ((this.digits[(this.lastIdx - 6)] * 1000000) + (if this.numDigits < 8: 0 else: (this.digits[(this.lastIdx - 7)] * 10000000))))))))))))))))
  this.asIntBeInst = asIntBeInstExpr
  this.asIntBeInstFlag = true
  return this.asIntBeInst

proc fromFile*(_: typedesc[Bcd], filename: string): Bcd =
  Bcd.read(newKaitaiFileStream(filename), nil, nil)