mkGeneric 1.3 Manual

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted. The author makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. By use of this software the user agrees to indemnify and hold harmless the author from any claims or liability for loss arising out of such use.
 

 CONTENTS

mkGeneric 1.3 - Some useful new commands for Tcl/Tk 8.

Introduction
Functions
Commands
Notes
Examples
Installation
Changes
Author

 INTRODUCTION

mkGeneric is a collection of new Tcl commands that I have always missed in the regular Tcl kernel. There is nothing really spectacular about this package, but it certainly contains some useful things like math functions and new list and loop commands.

 FUNCTIONS

expr min( Value1, Value2 )

Returns the lesser of the two values. Both values can be of type integer or double. If both values are of type integer, an integer is returned, otherwise a double.

expr max( Value1, Value2 )

Returns the greater of the two values. Both values can be of type integer or double. If both values are of type integer, an integer is returned, otherwise a double.

expr round2( Value1, Value2 )

Rounds Value1 to the next boundary of Value2. Both values can be of type integer or double. If both values are of type integer, an integer is returned, otherwise a double.

expr floor2( Value1, Value2 )

Rounds Value1 to the next lower boundary of Value2. Both values can be of type integer or double. if both values are of type integer, an integer is returned, otherwise a double.

expr ceil2( Value1, Value2 )

Rounds Value1 to the next upper boundary of Value2. Both values can be of type integer or double. if both values are of type integer, an integer is returned, otherwise a double.

expr isint( Value )

Returns 1, if the value is of type integer, otherwise 0. Value must be numeric.

expr isdouble( Value )

Returns 1, if the value is of type double, otherwise 0. Value must be numeric.

expr iseven( Value )

Returns 1, if the value is even, otherwise 0. Value must be an integer.

expr isodd( Value )

Returns 1, if the value is odd, otherwise 0. Value must be an integer.

expr pi()

Returns the constant Pi, equivalent to 2*acos(0). Inside the package it is stored as a constant and therefore retrieved faster.

expr e()

Returns the Euler constant, equivalent to exp(1). Inside the package it is stored as a constant and therefore retrieved faster.

 COMMANDS

lassign List ?VarName ...?

Assigns all elements in List to the given VarNames from left to right. I.e. the first element in List is assigned to the first VarName, the second element to the second VarName and so on. If there are more elements in List than VarNames exist, the remaining elements are returned as a list. If there are more VarNames than elements in List, the remaining VarNames are left unchanged.

leval List Command

For each element in List the given Command is evaluated, treating the element as a sublist and passing all elements of this sublist to Command as arguments. If an element of List is not a sublist but a single value, Command is called with one argument, the value. If an element of List is a sublist of n elements, Command is called with n arguments. If it is not known, how many elements will be in each sublist, it is recommended to define the special argument "args" as the last argument in the Command definition. The leval command returns a list of the evaluation results, corresponding to the elemens in List.

lstat ?Option? ?-force? List

Statistic functions that operate only on the numeric elements of the given List. If an element of List is non-numeric, an error will be returned, unless option -force is specified. If -force is specified, all non-numeric elements in List will be ignored. Except for option count, the result is returned as a floating point.

lstat count ?-force? List
Returns the number of all numeric elements in List.

lstat min ?-force? List
Returns the least of all numeric elements in List, or nothing, if there are no numeric elements.

lstat max ?-force? List
Returns the greatest of all numeric elements in List, or nothing, if there are no numeric elements.

lstat sum ?-force? List
Returns the sum of all numeric elements in List, or nothing, if there are no numeric elements.

lstat avg ?-force? List
Returns the average of all numeric elements in List, or nothing, if there are no numeric elements.

linter ?List ...?

Returns a sorted list that contains all elements that are found in all of the specified lists. This is the logical intersection of a number of lists.

lminus ?List ...?

Returns a sorted list that contains all remaining elements after a substraction operation. This is the locigal substraction of one list from another list. If two lists are specified, the result is "List1 minus List2", i.e. all elements of List1 that are not in List2 are returned.

lunion ?List ...?

Returns a sorted list that contains all elements of all given lists, eliminating multiple occurances of elements. That is, each element is reported exactly one time, regardless of how often it appears in the given lists.

linlist List Element

Returns 1, if Element can be found in List, otherwise 0.

ldelete List ?Index ...?

Deletes all elements specified by the given Indexes and returns the resulting, shorter list. An Index can be specified as an integer, "end", or "end-"integer.

lextend List Value

If Value is not yet an element of List, Value is appended to the list and the extended list is returned. Otherwise, the command simply returns List.

lshrink List Value

If Value is an element of List, Value is deleted from the list and the reduced list is returned. Otherwise, the command simply returns List.

lchange List OldValue NewValue

If OldValue is an element of List, OldValue is replaced by NewValue and the modified list is returned. Otherwise, the command simply returns List.

lprev List Element

Searches Element in List and returns the element right before Element, i.e. whos index is one less than that of Element. If Element is not in List, an error is returned. If Element is the first element in List, nothing is returned.

lnext List Element

Searches Element in List and returns the element right after Element, i.e. whos index is one greater than that of Element. If Element is not in List, an error is returned. If Element is the last element in List, nothing is returned.

loop VarName First Last ?Incr? Body

Provides a fast and flexible loop command, where the start and end values as well as any increment are calculated prior to executing the body. First, Last and Incr can be specified as integer or double. VarName is treated as an integer, if all of First, Last and Incr are of type integer, otherwise it is treated as a double. If Incr is omitted, it defaults to 1. If Incr is zero, an error is returned. If Last is greater than First, but Incr is negative, Body will not be executed. If First is greater than Last, but Incr is positive, Body will not be executed either. If the first character of Incr is a ?, Incr will be adjusted with the correct sign and Body will execute.

do Body while Expr

Implements a do-while loop. Body is executed as long as Expr evaluates to true. Unlike with the while loop, the do-while loop executes Body at least once, even if Expr returns false on its first evaluation.

try Script ?catch Pattern CatchScript ...? ?catch DefaultCatchScript? ?finally FinallyScript?

Enables exception handling in an easier and more flexible way than with the catch command. The try command works like the one in Java, and without breaking Tcl's philosophy. In fact, its structure is very similar to an if {} elseif {} else {} statement.

First, Script is evaluated. If it returns with an error (but not with break or continue) then the first CatchScript is executed, whos Pattern matches the first element in the errorCode variable (see the tclvars and error manpages for details). Pattern comparison is done like in string match or glob. If no matching Pattern is found, the DefaultCatchScript is executed, if defined. Finally, the FinallyScript is executed. The FinallyScript is guaranteed to be executed in any case, regardless if Script or a CatchScript returned an error, or called break or continue. It typically contains clean-up code.

The try command returns whatever Script or a CatchScript returns: A break or continue is always propagated immediately, without looking for a CatchScript. If Script raises an error, and no CatchScript is found, the error is propagated. If a CatchScript exists, the result of the CatchScript is returned, be it a normal result, an error or break or continue. However, if the FinallyScript raises an error or calls break or continue, it takes precedence and is immediately propagated.

The result of the try Script is stored in the global variable errorMsg, so that a CatchScript can work with it. There are now three variables that contain error information: The new variable errorMsg and the two standard variables errorCode and errorInfo.

Note that the fact that the try command works with exception patterns instead of fixed strings makes building exception hierarchies easy. Also, try can be nested and used in loops, since break and continue are propagated without triggering the exception handling.

throw ?errorCode? ?errorMsg? ?errorInfo?

This command is almost identical to the standard error command, except that the order of arguments is different and that it can be used without any arguments. The throw command becomes important in conjunction with the try command: try catches errors depending on the value of the errorCode variable. Therefore, the errorCode value is the first argument of throw. Like with the error command, throw raises an error condition that causes command interpretation to be unwound. If throw is used without any arguments, it rethrows the last error. This is useful if a catch script first wants to catch an error, do something and then propagate it further.

decode Expr List ?DefaultValue?

List must consist of an even number of elements. Each odd-numbered element in List is compared against Expr. If it matches, the element on the right of the matching element in List is returned. If there is no match and a DefaultValue is specified, it is returned. Otherwise the command returns nothing.

complete Expr List

Expr is compared against each of the elements in List to find a match. A match occurs if Expr is identical to one of the elements, or if it is a unique abbreviation for exactly one of them. In this case the matching element is returned, otherwise an error is created.

hexdump String

Takes String as binary data and returns a list containing a typical hexdump output. Each element of that list represents a printable line, showing 16 bytes of String in Hex and Ascii, preceeded by the starting address.

encrypt Key Data

Enciphers data with a 128 bit key. The algorithm used is TEA, the "Tiny Encryption Algorithm". TEA is small, fast, very secure, and it is public domain. The Key can be up to 16 bytes long and may be a binary string. The Data may also be a binary string. The returned string is always binary and has a length that is a non-zero integer multiple of 8, since TEA always encodes blocks of 64 bits. The internet is loaded with information and ready-to-use code about TEA. Just two links are http://www.ftp.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html and http://www.vader.brad.ac.uk/tea/tea.shtml Note that the output is independent of the "endianess" of a platform. Data encrypted on a little-endian system can be decrypted on a big-endian system an vice versa.

decrypt Key Data

The counterpart of encrypt. Deciphers a string that was returned from the encrypt command.

checksum ?-size 16|32? Data

The checksum command calculates a 16 or 32 bit cyclic redundancy check (CRC) code, depending on the -size option. If -size is not given, it defaults to 16. The algorithms are table based and very fast. The Internet provides plenty of information about CRC, e.g. on the very colorful site http://www.programmingparadise.com/utility/crc.html.

hash Data

Generates a hash value of the given Data based on the SHA-256 algorithm. SHA-256 is a one-way hash algorithm (see http://csrc.nist.gov/cryptval/shs.html) that produces a 256-bit (32 bytes) key upon any given input data. The security against collision attacks is 128 bits. The output of the hash command is identical for big-endian and little-endian platforms.

The command returns a binary string with a length of 32 bytes. Such algorithms are used for authentication, not to create secrecy. A typical application would be password verification. There is loads of information about SHA-256 in the internet, including example code.

options ?-allowargs|-nocomplain? ArrayName List ?OptionSpec ...?

This is a simple but efficient option processing routine. Options and their values are usually passed to a tcl command as a list of arguments. Often, an arbitrary number of additional arguments can follow the options. Options always start with a dash "-". Sometimes options and arguments are separated by a "--" to clearly denote the end of the options and the beginning of any arguments that follow. A typical example is lsort -descending -ascii -index 1 $myList. It shows that options can have either no value (like -descending, -ascii) or one value (like -index 1). So there are three options, one of them having a value, and there is one argument, $myList. The man page for lsort shows that there are nine allowed options in total.

The options command's job is to detect options, eventually retrieve their values, leave the remaining arguments untouched, and create a meaningful error message for options that are not allowed. List is expected to be a list containing options, values, a "--", and arguments. Allowed options are represented by option specifications. For options with no value, the option specification is simply the name of the option, e.g. -descending. For options with a value, an option specification consists of two or three elements: The first is the option name, the second a list of allowed values, or "*" for all values. The optional third argument is a default value that is used if the option is not found in List.

All found options and any values are stored in ArrayName. The index of each element in ArrayName is the name of the option, the value is the option's value. If the option does not accept a value, then "1" is used. Therefore, ArrayName contains only those options that were somehow found in List. The only exception are options for which default values were defined in any of the option specs.

Without -allowargs or -nocomplain, List is expected to contain only options and values, but no "--" and no arguments. The command will raise an error if an unspecified option is found. With -allowargs, List may contain a "--" and arguments. The command assumes that everything before the "--", or everything before the first element that does not start with a dash "-", belongs to the option part of List. The argument part is returned by the command, excluding any "--". For -nocomplain, the command will try to match as much as possible and return everything that did not look like an option. No error will be created for unknown options, but it will be for specified options with an unspecified option value.

/# Script

The /# command can be used to comment out blocks of code instead of just a single line. Unlike the # command, which takes a variable number of arguments and therefore only comments out to the end of a line (unless there's a \ at the end), this one takes exactly one argument. To comment out multiple lines of code, /# must be followed by a {, and the block must be closed with a }. If the block is closed with e.g. #/ }, then text editors that feature syntax highlighting for block comments can support this.

 NOTES

64 Bit Platforms

The implementation of TEA assumes that an unsigned long is 32 bit wide. The encrypt and decrypt commands won't work properly on platforms where this is different (is this so on 64 bit platforms???).

 EXAMPLES

List Commands

  % lassign {1 2} one two; puts "$one $two"
  1 2
  % leval {one two} puts
  one
  two
  {} {}
  % lstat avg -force {1 2 a 3 4}
  2.5
  % linlist {a b c} b
  1
  % ldelete {a b c d e} 1 3
  a c e
  % linter {a b c d z} {x b y d}
  b d
  % lunion {a v m v} {v b}
  a b m v
  % lminus {a b c d z} {x b y d}
  a c z
  % lextend {a b c} a
  a b c
  % lshrink {a b a c} a
  b c
  % lchange {a b a c} a A
  A b A c
  

Loop Command

  % loop x 1 5 -1 { puts $x }
  % loop x 1 5 2 { puts $x }
  1
  3
  5
  % loop x 4 2 ?.8 { puts $x }
  4.0
  3.2
  2.4
  

Exception Handling

  % try {
    puts "calculate something"
    expr 1/0
    puts "done calculating"
  } catch ARITH {
    puts "arithmetic error: $errorMsg / $errorCode"
  } catch {
    puts "general error: $errorMsg / $errorCode"
  } finally {
    puts "clean up here"
  }
  calculate something
  arithmetic error: divide by zero / ARITH DIVZERO {divide by zero}
  clean up here
  %
  % try {
    if { 5 > 2 } {
      throw RANGE.OVERFLOW "value greater than 2!"
    }
  } catch RANGE.OVERFLOW {
    puts "range overflow error: $errorMsg / $errorCode"
    throw
  } catch RANGE.* {
    puts "other range error: $errorMsg / $errorCode"
  } finally {
    puts "clean up here"
  }
  range overflow error: value greater than 2! / RANGE.OVERFLOW
  clean up here
  value greater than 2!                   <-- exception is rethrown
  %
  % for { set i 0 } { $i < 10 } { incr i } {
    try {
      if { $i == 5 } break
      puts -nonewline $i
    }
  }
  01234%                                  <-- break is not caught!
  %
  % for { set i 0 } { $i < 10 } { incr i } {
    try {
      if { $i == 5 } break
    } finally {
      puts -nonewline $i
    }
  }
  012345%                                 <-- finally is always called!
  

Options Command

  % proc p { foo args } {
    options opt $args -decreasing {-index *} {-color {red green blue} red}
    parray opt
  }
  % p dummy
  opt(-color) = red
  % p dummy -index 5 -decr -color g
  opt(-color)      = green
  opt(-decreasing) = 1
  opt(-index)      = 5
  % p dummy -color yellow
  bad value "yellow": must be red, green, or blue
  % options -nocomplain x {-notmyopt foo -myopt xxx -- arg1 arg2} {-myopt *}
  -notmyopt foo arg1 arg2
  % options -allowarg x {-notmyopt foo -myopt xxx -- arg1 arg2} {-myopt *}
  bad option "-notmyopt": must be -myopt
  % options -allowarg x {-myopt xxx -- arg1 arg2} {-myopt *}
  arg1 arg2
  

Encryption etc.

  % set sEncrypted [encrypt myKey123 HereIsSomeData]
  (some garbage here)
  % decrypt myKey123 $sEncrypted
  HereIsSomeData
  %
  % checksum AnyData
  29951
  % format %u [checksum -size 32 TheQuickBrownFoxJumpsOverTheLazyDog]
  2721577234
  %
  % set sEncrypted [hash MyPassword]
  (more garbage here)
  % string length $sEncrypted
  32
  %
  

Miscellaneous Commands

  % decode g {r red g green b blue} undefined
  green
  % decode y {r red g green b blue} undefined
  undefined
  %
  % complete gr {red green blue}
  green
  % complete yellow {red green blue}
  bad value "yellow": must be red, green, or blue
  % complete bl {red green blue black}
  ambiguous value "bl": must be red, green, blue, or black
  %
  % join [hexdump "the quick brown fox jumps over the lazy dog"] "\n"
  000000  74 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20  the quick brown
  000010  66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74  fox jumps over t
  000020  68 65 20 6c 61 7a 79 20 64 6f 67                 he lazy dog
  %
  % /# { puts {
  nothing will
  be printed
  } #/ }       ;# the #/ is useful for editors with syntax highlighting
  

 INSTALLATION

 General

mkGeneric is written in C and comes with a DLL for Windows. On Unix, the package needs to be compiled into a shared library first (see below). mkGeneric works with Tcl/Tk version 8.0 and higher. It has been stubs-enabled with version 8.2.

To install, place the directory "mkGeneric1.3" in one of the directories contained in the global Tcl variable "auto_path". For a standard Tcl/Tk installation, this is commonly "c:/program files/tcl/lib" (Windows) and "/usr/local/lib" (Unix).

Compiling

To compile the package, just provide the correct path to "tcl.h" and link against "tcl8x.lib" (Windows) or "libtcl8x.so" (Unix) respectively. If you use stubs, define USE_TCL_STUBS and link against "tclstub8x.lib" (Windows) or "libtclstub8x.a" (Unix) instead.

For Visual C++, the following command should work:

  cl /I c:/progra~1/tcl/include /D USE_TCL_STUBS /c mkGeneric13.c
  link c:/progra~1/tcl/lib/tclstub83.lib /dll mkGeneric13.obj

On Linux 2.2, this here works fine:

  gcc -shared -DUSE_TCL_STUBS -ltclstub8.3 -o mkGeneric13.so mkGeneric13.c

Test

Test the installation by opening a tclsh or wish and entering "package require mkGeneric". The string "1.3" should appear. If it fails, "cd" into the directory "mkGeneric1.3" and load it directly with "load ./mkGeneric13.dll" (Windows) or "load ./mkGeneric13.so" (Unix). If no error occured, it succeeded and something must be wrong with the location of "mkGeneric1.3".

 CHANGES

Changes from 1.0 to 1.1

Changes from 1.1 to 1.2

Changes from 1.2 to 1.3

 AUTHOR

Michael Kraus
mailto:michael@kraus5.de
http://mkextensions.sourceforge.net