This Is How I Glyph – Tcl Namespaces (Part 2)

This is the second in a series of posts about the Tcl namespace command. Each subsequent post will cover more complex uses of namespaces. While this topic is strictly related to Tcl only, it could help you when developing more complex Glyph scripts.

The topics to be covered in this post depend upon a clear understanding of concepts outlined in Tcl Namespaces (Part 1). If you haven’t already, then please review them before continuing.

Subcommands

If you look at the Tcl command manual pages, you will find that many commands support subcommands. One example would be the Tcl string command with its 19 subcommands. A Glyph example would be the pw::Application command and its more than 70 subcommands!

In the example code below equal, map, and range are subcommands of the string command. Similarly, getVersion and getCAESolver are subcommands of the pw::Application command.

A Tcl command with one or more subcommands is also known as a command ensemble. The Tcl namespace command can be used to create your own command ensembles.

package require PWI_Glyph 2.18.0

set s1 "Hello World!"
set s2 "hello world!"
set fromToPairs {Hello Goodbye World Birdie}
puts "string"
puts " equal => [string equal $s1 $s2]"
puts " equal -nocase => [string equal -nocase $s1 $s2]"
puts " map => '[string map $fromToPairs $s1]'"
puts " range => '[string range $s2 6 end-1]'"
puts "pw::Application"
puts " getVersion => '[pw::Application getVersion]'"
puts " getCAESolver => '[pw::Application getCAESolver]'"

# Output:
# string
# equal => 0
# equal -nocase => 1
# map => 'Goodbye Birdie!'
# range => 'world'
# pw::Application
# getVersion => 'Pointwise V18.0R2C5'
# getCAESolver => 'CGNS'

The RangeInt Namespace

Before we get into ensembles, let’s take a look at a namespace called RangeInt. RangeInt supports the creation of named integer values with an associated min and max range. Attempting to assign a value outside this range will result in a Tcl error being triggered. Some example usage of RangeInt is given at the end of the code listing. RangeInt defines a variable named params_ and four procs named add, set, get, and check.

The params_ variable is a Tcl dictionary that is used to store the range and value for each of the named integers.

The add proc is used to create a new named value.

The set, and get procs are used to manipulate the integer values.

The check proc is used to validate values.

namespace eval RangeInt {
  variable params_ [dict create]

  proc add { name {val 0} {min inf} {max inf} } {
    variable params_
    dict set params_ $name MIN $min
    dict set params_ $name MAX $max
    RangeInt::set $name $val
  }

  proc set { name val } {
    variable params_
    if {[check $name $val >= MIN] && [check $name $val <= MAX]} {
      dict set params_ $name VAL $val
      return
    }
    ::set min [dict get $params_ $name MIN]
    ::set max [dict get $params_ $name MAX]
    return -code error "Invalid value: $min <= $val <= $max"
  }

  proc get { name {defaultVal 0} } {
    variable params_
    return [expr {[dict exists $params_ $name VAL] ? \
      [dict get $params_ $name VAL] : $defaultVal}]
  }

  proc check { name val op key } {
    variable params_
    ::set limit [dict get $params_ $name $key]
    return [expr [list "$limit" == "inf" || $val $op $limit]]
  }
}

RangeInt::add i1 888 ;# range -infinity ... +infinity
RangeInt::add i2 5 0 20 ;# range 0 ... 20
RangeInt::add i3 -3 -10 ;# range -10 ... +infinity
RangeInt::add i4 -5 inf -5 ;# range -infinity ... -5
puts "i1 == [RangeInt::get i1]"
puts "i2 == [RangeInt::get i2]"
puts "i3 == [RangeInt::get i3]"
puts "i4 == [RangeInt::get i4]"
puts {}
RangeInt::set i1 500
puts "i1 == [RangeInt::get i1]"
puts {}
catch {RangeInt::set i2 21} msg
puts "error '$msg'"
puts "i2 == [RangeInt::get i2]"

# Output:
# i1 == 888
# i2 == 5
# i3 == -3
# i4 == -5
#
# i1 == 500
#
# error 'Invalid value: 0 <= 21 <= 20'
# i2 == 5

Namespace Command Ensembles

Converting a namespace into a command ensemble is simple. First, you add a call to the namespace export command for each proc that you want to be an ensemble subcommand. Then, to actually create the ensemble, add a call to the namespace ensemble create command. This call must be made after all the subcommand procs have been exported. The bolded code fragments below show these changes.

Please notice that the check proc is not exported. It is a (private) helper proc that is only to be used by the namespace itself. Since check was not exported, attempting to call RangeInt check will result in an “unknown or ambiguous subcommand” error.

namespace eval RangeInt {
  variable params_ [dict create]

  namespace export add
  proc add { name {val 0} {min inf} {max inf} } {
    variable params_
    dict set params_ $name MIN $min
    dict set params_ $name MAX $max
    RangeInt::set $name $val
  }

  namespace export set
  proc set { name val } {
    variable params_
    if {[check $name $val >= MIN] && [check $name $val <= MAX]} {
      dict set params_ $name VAL $val
      return
    }
    ::set min [dict get $params_ $name MIN]
    ::set max [dict get $params_ $name MAX]
    return -code error "Invalid value: $min <= $val <= $max"
  }

  namespace export get
  proc get { name {defaultVal 0} } {
    variable params_
    return [expr {[dict exists $params_ $name VAL] ? \
      [dict get $params_ $name VAL] : $defaultVal}]
  }

  # do not export check - it is for internal use only
  proc check { name val op key } {
    variable params_
    ::set limit [dict get $params_ $name $key]
    return [expr [list "$limit" == "inf" || $val $op $limit]]
  }

  namespace ensemble create
}

# No more RangeInt:: needed!
RangeInt add i1 888 ;# range -infinity ... +infinity
RangeInt add i2 5 0 20 ;# range 0 ... 20
RangeInt add i3 -3 -10 ;# range -10 ... +infinity
RangeInt add i4 -5 inf -5 ;# range -infinity ... -5
puts "i1 == [RangeInt get i1]"
puts "i2 == [RangeInt get i2]"
puts "i3 == [RangeInt get i3]"
puts "i4 == [RangeInt get i4]"
puts {}
RangeInt set i1 500
puts "i1 == [RangeInt get i1]"
puts {}
catch {RangeInt set i2 21} msg
puts "error '$msg'"
puts "i2 == [RangeInt get i2]"
puts {}
puts "check == [RangeInt::check i2 5 <= MAX]" ;# direct call ok
catch {RangeInt check i2 5 <= MAX} msg
puts "error '$msg'"

# Output (same as above):
# i1 == 888
# i2 == 5
# i3 == -3
# i4 == -5
#
# i1 == 500
#
# error 'Invalid value: 0 <= 21 <= 20'
# i2 == 5
#
# check == 1
# error 'unknown subcommand "check": must be add, get, or set'

Namespace or Ensemble?

There are no hard and fast rules when choosing between plain old namespace procs (RangeInt::add) or going the extra step of exporting procs as ensemble subcommands (RangeInt add). They both achieve the important encapsulation and scoping desired when building a complex Tcl or Glyph library.

I find that the copious use of :: in a Tcl script can be distracting and make the code harder to read. I lean towards using command ensembles for everything except the outer-most namespace. For instance, Glyph uses the pw namespace to protect the other nested, Pointwise specific command ensembles. That is, the pw namespace is not itself an ensemble. However, the nested namespaces such as pw::Grid are implemented as ensembles with subcommands like pw::Grid getAll.

The code below is a simplified implementation of the nested pw::Grid and pw::Database command ensembles inside the plain old pw namespace.

namespace eval pw {

  # The pw::Grid command ensemble
  namespace eval Grid {

    namespace export getAll
    proc getAll { args } {
      ...
    }

    namespace export getCount
    proc getCount { args } {
      ...
    }

    namespace ensemble create
  }

  # The pw::Database command ensemble
  namespace eval Database {

    namespace export getAll
    proc getAll { args } {
      ...
    }

    namespace export getCount
    proc getCount { args } {
      ...
    }

    namespace ensemble create
  }
}

# pw::Grid getAll, pw::Grid getCount, pw::Database getAll, and
# pw::Database getCount now exist

Until Next Time

This post covered the creation of namespace command ensembles. I will be covering additional namespace wizardry in future posts. If you want to learn more about namespaces, then take a look at the full Tcl namespace documentation.

This Is How I Glyph – Tcl Namespaces (Part 3) | Another Fine Mesh

About David Garlisch

Illini by birth, Texan by choice.
This entry was posted in Applications, Software and tagged , , . Bookmark the permalink.

2 Responses to This Is How I Glyph – Tcl Namespaces (Part 2)

  1. Pingback: This Is How I Glyph – Tcl Namespaces (Part 1) | Another Fine Mesh

  2. Pingback: This Is How I Glyph – Tcl Namespaces (Part 3) | Another Fine Mesh

Leave a Reply