Mod /
Struct
Search:  


1.  Validated Structs

The purpose of *struct is to generate access functions for validated and type checked manipulation of data fields stored in a Tree. Principly, this leverages static code checking to verify the validity of data accesses.

A *struct definition create 6 access functions for manipulating data fields. These functions start with single letter prefixes, to which is appended _ and structname.

Thus for the user struct (in the example below) we have:

PrefixDescriptionSignature
SSet/update fields in the record.proc S_user {t id args}
IIncrement an integer or double field.proc I_user {t id field {amt 1}}
GGet a value from a field.proc G_user {t id field
NNew record instantiation.proc N_user {t pos args}
AAppend a string to a field.proc A_user {t id field args}
LLappend a value to a field.proc L_user {t id field args}

These so called S I G N A L functions provide controlled access to data and can signal warning/errors at both compile and run time.

2.  Example

Structs are particularly useful in conjuction with *tables. Here is an example:

namespace eval ::myapp {

    *struct new user {
        { name      {}  "Name of user" }
        { age       0   "Age of user" -type Int }
        { class     {}  "List of classes" }
        { prefix    {}  "String prefix" }
    }

   *table users {
        ^    user
        bill { "Bill Hale"  29    }
        joe  { "Joe Black"  19    }
        fan  { "Fan Tan"    9     }
    }

    proc DoBad {u id} {
        # Do some invalid operations to generate warnings.
        puts [G_user $u $id BADKEY]
        S_user $u $id age BADVAL
        I_user $u $id name
    }

    proc Main {} {
        variable users
        set u $users
        S_user $u bill   name "William Hale" age 8
        I_user $u joe    age 23.99
        A_user $u fan    prefix A B C
        N_user $u end   name "Fred Fay" age 12
        if {0} {
           DoBad $u bill
        }
    }

    eval Main
}

3.  Static Warnings

When above example is run it outputs:

% wize -Wall /tmp/myapp.tcl

/tmp/myapp2.tcl:25: warning: for argument #3 "field", the value
  "BADKEY" does not match type <Choice name age class prefix>  
  for "G_user $u $id BADKEY" in proc [::myapp::DoBad] <types,4>.
/tmp/myapp2.tcl:26: warning: for argument #4 "args", the value 
  "BADVAL" does not match type <Int>  for "S_user $u $id age 
  BADVAL" in proc [::myapp::DoBad] <types,4>.
/tmp/myapp2.tcl:27: warning: for argument #3 "field", the value 
  "name" does not match type <Choice age>  for "I_user $u $id name"
  in proc [::myapp::DoBad] <types,4>.

The interesting thing to note is that warnings are generated for code that is not even executed.

Also note that calling I_user on a non int/double field results in a warning.

See 6.1  Typechecking for details on runtime typechecking.

4.  Details

A *struct declaration is a list of lists, each element of which has up to 3 fixed values:

  • name - the name of the field
  • init - the initial value for the field
  • desc - a comment or description of the field.

optionally follow by name/value pair options:

NameDescription
-labelAn optional label
-notnullField is non-empty and required for N_*
-typeThe field type eg. Int, Double

When *struct is called, it creates 6 access functions in the users namespace. It also creates an array to stores various other information. This array may be used by *table.

5.  New Records

New records are inserted with a call to N_name, eg.

  N_user $u 0   name "Bill Ray" age 10

The pos argument is usually 0. The record gets initialized with the default values and then is any passed name/values. Although warnings will occur, it is not an error to pass in extra, undefined fields (unless -fixed or -typecheck is true). Later updates to these undefined fields will also cause warnings but not errors.

Options can be given to N_ before pos:

NameDescription
-label strLabel to add to node
-tags strList of tags to add

eg.

   N_user $u -label fred end   name "Fred Fay" age 12

6.  Options

Following are options to *struct:

NameDescription
-evalsInsert eval code via name/value pairs
-fixedDo not allow creation of new fields
-pevalsPost eval code via name/value pairs
-prefsPrefixes overrides via name/value pairs
-novarDo not save struct defs in local user array
-typecheckEnforce type strict type checking.

6.1  Typechecking

By default typechecking consists of simple compile-time advisory warnings. However argument types can be strictly enforced at runtime by setting -typecheck to true, eg.

   *struct new user {
      { First     {}  "First name of user" }
      { Last      {}  "Last name of user" }
      { Age       0   "Age of user" -type Int }
  } -typecheck 1

  *table managers {
     ^     user
     bob   { Bob    Brown    19  }
     tom   { Tom    Wake     18  }
     bill  { Bill   Williams 17  }
  }

  $managers update bill   Last Barry
  S_user $managers bill   Age xyz

which results in a runtime error:

for argument #4 "args", the value "xyz" does not match type <Int> 
    while executing
"S_user $managers bill   Age xyz"
    (file "mgrs.tcl" line 16)

Note that checking is provided by wize (via [info checking]). This occurs at the C level, so runtime overhead is minimal.

6.2  Prefix Override

Lets say you don't like the name prefixes N_ etc, or you don't need append, or lappend. The -prefs option provides a way to override these:

   *struct new foo {
      { name {} "A name" }
      { age  0  "The age" }
   } -prefs { N New S Set L {} A {} }

   set t [tree create]
   set id [New_foo $t 0]
   Set_foo $t $id name "Larry Flint"

6.3  Adding Code

You can add code to the begin/end of commands using -evals/-pevals like so:

   *struct new foo {
      { name {} "A name" }
      { age  0  "The age" }
   } -evals {
      I   {if {$field == "age" && $amt<0} { error "age < 0"}
      A   {if {$field == "age"} { error "append age invalid"}
   } -pevals {
      I   {if {$val < 0} { error "value must be >= 0: $val" }
   }

   set t [tree create]
   set id [N_foo $t 0]
   I_foo $t $id age -1

Note that this example is contrived, as it would be lower overhead to just declare the age field as:

  *struct new foo {
      { name {} "A name" }
      { age  0  "The age" -type {int -min 0} }
  } -typecheck 1

Alternatively, you could just use a write trace.

7.  Doing More

Since tree supports variable traces it is an simple matter to enhance *struct to do more checking or add integrity constraints.

See the tree man page for details.

© 2008 Peter MacDonald

Page last modified on August 28, 2010, at 01:14 PM