#!/bin/sh
# the next line restarts using tclbsh \
exec tclbsh "$0" "$@"

########################################################################
#
# This is an example of how to use the bita-facilities.
#
# At our place we prefer for signal processing a file format, which
# --- as usual --- contains a descriptive header and then the binary
# data. 
#
# This program converts .tdf-files, the format of which is described
# below, to ascii.
#
# The header of a tdf-file is in ASCII and is separated from the
# binary part by ^U (21,0x15,025). It is line oriented and contains on
# every line a keyword-value pair. The keyword must not contain any
# white space and is separated from the value by a colon. Everything
# after the colon is the value.
#
# It is usually up to a certain program or set of programs to
# interprete the keywords and their values, however one keyword is
# mandatory, namely `Format'. It describes the data stored in the
# binary part. Example format descriptions are
#    100F
#    10I 10US 1C
#    1001(2F)
# describing binary parts containing
#    100 float values
#    10 int 10 unsigned short and 1 char values
#    1001 pairs of float values
#
# The general theme should be clear from the examples. In general the
# following type specifiers are defined:
#     US - unsigned short
#      S - short
#      U - unsigned
#      I - int
#      F - float
#      D - double
# Between the count and the type specifier cannot be any
# blanks. However between count-type pairs may be white
# space. Parentheses may be used to any depth. 
#
# Typically a certain program expects a general pattern of a format
# and can easily parse it with scanf. For example a program expecting
# a complex vector might always want to see a format of the general
# form `n (2F), if a complex value is stored as a pair of floats. It
# may then use s.th. like
#    if( 1!=sscanf(format, "%d ( 2F ) %n", &count, &n) || format[n] ) {
#      sprintf(stderr, "Wrong format\n");
#      exit(1);
#    }
# To parse and check the format pattern.
#
#
# Well now, this program doesn't recognize a special format pattern
# but tries to recognize every valid format and converts the binary
# part of a .tdf-file to ascii.
#
########################################################################

set Program $argv0

########################################################################
proc readHeader {f Head} {
  global Program fName
  upvar $Head head

  set i 0
  while { ![eof $f] && "\025"!=[set line [read $f 1]] } {
    if {$line=="\n"} continue
    gets $f l
    append line $l
    set head($i) $line
    incr i
  }
  if {"\025"!=$line} {
    puts stderr "$Program: no binary marker found in file `$fName'"
    exit 1
  }
}
########################################################################
proc getFormat {Head} {
  upvar $Head head
  global fName Program

  #parray head
  foreach x [array names head] {
    if { [regexp {^ *Format *:(.*)} $head($x) dummy format] } {
      return $format
    }
  }

  puts stderr "$Program: no keyword `Format' found in file `$fName'"
  exit 1
}
########################################################################
proc chopCount {f} {
  upvar $f format
  global fName Program

  if { ![regexp {^ *([0-9]+| *\))} $format dummy count] } {
    puts -nonewline stderr \
	"$Program: number expected at suffix `$format' of format spec "
    puts stderr "in file `$fName'"
    exit 1
  }
  regsub {^ *([0-9]+| *\))} $format {} format
  return $count
}
########################################################################
proc chopType {f} {
  upvar $f format
  global fName Program

  if { ![regexp {^ *(S|US|I|U|F|D|\()} $format dummy type] } {
    puts -nonewline stderr \
	"$Program: wrong type at suffix `$format' of format spec "
    puts stderr "in file `$fName'"
    exit 1
  }
  regsub {^ *(S|US|I|U|F|D|\()} $format {} format
  return $type
}
########################################################################

proc binConvert {file format b} {
  global Program fName

  ## translation table of type-specifiers-->bita types
  set trans(S) Short
  set trans(US) UShort
  set trans(I) Int
  set trans(U) Unsigned
  set trans(F) Float
  set trans(D) Double
  set keepFormat $format

  ## work your way through the whole format string
  while {[string length $format]} {

    ## the count
    set count [chopCount format]
    if { "$count"==")" } {
      return ")$format"
    }

    ## the type
    set type [chopType format]
    if { "$type"=="(" } {
      for {set i 1} {$i<=$count} {incr i} {
	puts "${b}## no. $i of $count"
	set restForm [binConvert $file $format "$b  "]
      }
      #puts "`$restForm'"
      if { ![string match {)*} $restForm ] } {
	puts -nonewline stderr \
	    "$Program: missing matching ')' at suffix `$count\($format' "
	puts stderr \
	    "of format spec in file `$fName'"
	exit 1
      }
      set format [string range $restForm 1 end]
      continue
    }

    ## the conversion
    puts "${b}## $count$type"
    $trans($type) buf
    set c [buf read end $file $count]
    
    # Instead of crunching all the binary data into a string, lets get
    # it value by value.
    for {set i 0} {$i<$c} {incr i} {
      puts "${b}[buf get $i $i]"
    }
    if { $c!=$count } {
      puts -nonewline stderr \
	  "$Program: unexpected eof while reading `$count$type' "
      puts -nonewline stderr \
	  "at suffix `$count$type $format' of format spec "
      puts stderr "in file `$fName'"
      exit 1
    }
  }
  return {}
}
  
########################################################################

if { $argc!= 1 } {
  puts stderr "usage: $Program tdfFile"
  exit 1
}

set fName [lindex $argv 0]
set file [open $fName r]
readHeader $file head
set format [getFormat head]

puts "## Header:"
set N [llength [array names head]]
for {set i 0} {$i<$N} {incr i} {
  puts $head($i)
}
#puts "Format = `$format'"
set restFormat [binConvert $file $format {}]

if { [llength $restFormat] } {
  puts -nonewline stderr \
      "$Program: surplus ')' at suffix `)$restFormat' of format spec "
  puts stderr "in file `$fName'"
  exit 1
}

if { [string length [read $file 1]] } {
  puts -nonewline stderr \
      "$Program: file `$fName' contains more data than described "
  puts stderr \
      "in format spec"
  exit 1
}
close $file
